any

The `Any` type with dynamic typing and runtime type checks.

moon add tonyfettes/any@0.1.5
Download zip
Version
0.1.5
License
Apache-2.0
Last updated
8 months ago
Downloads
37K
README

#tonyfettes/any

A type-safe dynamic typing library for MoonBit that provides runtime type inspection and transformation capabilities.

#Overview

tonyfettes/any enables safe runtime type handling in MoonBit through the Any type. It allows you to:

  • Store values of any type and retrieve them with type safety
  • Transform values between different types with compile-time guarantees
  • Chain type-conditional operations on dynamic values
  • Get informative error messages when type mismatches occur

#Installation

Add this package to your moon.mod.json:

moon update moon add tonyfettes/any

#Quick Start

// Store any value
let any = @any.of(42)

// Query type information
@json.inspect(any.type_name(), content="Int")

// Retrieve with type checking
let value : Int = any.to()
@json.inspect(value, content=42)

// Type mismatch raises TypeMismatch error
let value : Result[Bool, @any.TypeMismatch] = try! any.to()
@json.inspect(value, content={
"Err": { "TypeMismatch": { "expect": "Bool", "actual": "Int" } },
})

#Core API

#Creating Any Values

#Any::of[T](value: T) -> Any

Wraps a value of any type into an Any container.

let int_any = @any.of(42)
@json.inspect((int_any.to() : Int))
let str_any = @any.of("hello")
@json.inspect((str_any.to() : String))
let arr_any = @any.of([1, 2, 3])
@json.inspect((arr_any.to() : Array[Int]))

Special behavior: Wrapping an Any value returns the original value (identity operation):

let a0 = @any.of(42)
let a1 = @any.of(a0)
@json.inspect(physical_equal(a0, a1), content=true)

#Type Information

#Any::type_info(self: Any) -> TypeInfo

Returns the complete type information including both type ID and type name.

let any = @any.of([1, 2, 3])
let info = any.type_info()
@json.inspect(
physical_equal(info.id(), @any.of([1]).type_info().id()),
content=true,
)
@json.inspect(info.name(), content="Array[Int]")

#Any::type_id(self: Any) -> TypeId

Returns the runtime type identifier for the stored value.

let any = @any.of(42)
let id = any.type_id()
@json.inspect(physical_equal(id, @any.of(100).type_id()), content=true)

#Any::type_name(self: Any) -> String

Returns the human-readable type name of the stored value.

let any = @any.of("hello")
@json.inspect(any.type_name())
let any = @any.of([1, 2, 3])
@json.inspect(any.type_name(), content="Array[Int]")
let any = @any.of(Some(42))
@json.inspect(any.type_name(), content="Int?")

#Retrieving Values

#Any::to[T](self: Any) -> T raise TypeMismatch

Unwraps the value with type checking. Raises TypeMismatch if the actual type doesn't match the expected type.

let any = @any.of(42)
let value : Int = any.to()
@json.inspect(value, content=42)
let wrong : Result[String, _] = try! any.to()
@json.inspect(wrong, content={
"Err": { "TypeMismatch": { "expect": "String", "actual": "Int" } },
})

#Any::try_to[T](self: Any) -> T?

Safely attempts to unwrap the value. Returns None if types don't match, Some(value) otherwise.

let any = @any.of([1, 2, 3])
let value : Array[Int]? = any.try_to()
@json.inspect(value is Some([1, 2, 3]), content=true)
let wrong : String? = any.try_to()
@json.inspect(wrong is None, content=true)

#Any::unsafe_coerce[T](self: Any) -> T

Performs unchecked type coercion. Use with extreme caution - incorrect usage leads to undefined behavior.

// Only use when you're absolutely certain about the type
let any = @any.of(42)
let value : Int = any.unsafe_coerce()
@json.inspect(value, content=42)

#Type Transformation

#Any::map[T, R](self: Any, f: (T) -> R raise?) -> Any raise?

Conditionally applies a function if the value matches the expected type. Returns the transformed value wrapped in Any, or returns the original Any unchanged if types don't match.

This enables elegant type-conditional pipelines:

fn any_to_int(any : @any.Any) -> Int raise {
any
.map((int : Int) => int) // If Int, return as-is
.map((str : String) => @strconv.parse_int(str)) // If String, parse it
.map((double : Double) => double.to_int()) // If Double, convert it
.to() // Finally extract the Int
}

@json.inspect(any_to_int(@any.of(42)))
@json.inspect(any_to_int(@any.of("43")))
@json.inspect(any_to_int(@any.of(44.0)))

The map function is particularly powerful for:

  • Type-safe dispatching: Handle different types with different logic
  • Fallthrough behavior: Non-matching types pass through unchanged
  • Exhaustiveness checking: Chain maps to handle all possible types

Example with Unit return type (side effects only):

let any = @any.of("hello")
any
.map((int : Int) => println("Int(\{int})"))
.map((str : String) => println("String(\{str})")) // This executes
.to() // Ensures all cases are handled

#Error Handling

#TypeMismatch

The TypeMismatch error provides detailed information about type mismatches, carrying complete TypeInfo for both expected and actual types:

///|
pub suberror TypeMismatch {
TypeMismatch(expect~ : TypeInfo, actual~ : TypeInfo)
}

Traits implemented:

  • Show: Human-readable error messages
  • ToJson: JSON serialization for structured error handling

Example error output:

let any = @any.of([1, 2, 3])
let result : Result[Bool, @any.TypeMismatch] = try! any.to()
@json.inspect(result, content={
"Err": { "TypeMismatch": { "expect": "Bool", "actual": "Array[Int]" } },
})

The error message format:

TypeMismatch: expected type 'Bool' but found type 'Array[Int]'

JSON representation:

{ "TypeMismatch": { "expect": "Bool", "actual": "Array[Int]" } }

#Best Practices

  1. Prefer static typing: Use Any only when runtime type flexibility is necessary
  2. Use try_to() for optional extraction: Allocates less than handling TypeMismatch exceptions
  3. Chain map() for exhaustive handling: Ensures all expected types are covered
  4. Avoid unsafe_coerce(): Only use when performance is critical and types are guaranteed

#License

Apache-2.0

#Contributing

This is a MoonBit project. See AGENTS.md for contribution guidelines and coding conventions.

#
TypeMismatch

pub suberror TypeMismatch {
TypeMismatch(TypeInfo, TypeInfo)
}

Error raised when attempting to extract a value as an incompatible type. Carries both expected and actual runtime type ids so they can be converted to human-readable names for diagnostics.

#
Any

type Any

#
Any::map

fn[T, R] Any::map(self : Any, f : (T) -> R raise?) -> Any raise?

Conditionally apply f to the stored value if its runtime type matches T.

When it matches, the result of f is rewrapped with Any::of; otherwise the original Any is returned unchanged. Enables chaining to build exhaustive type-dispatch pipelines. Propagates any error (raise?) from f.

#
Any::of

#as_free_fn
fn[T] Any::of(value : T) -> Any

Wrap a value of type T in an Any container.

This records the runtime type id and stores the value in a type-erased box. If the provided value is already an Any, the original Any is returned (idempotent double-wrap elimination).

The first time a type id is encountered its human-readable name is cached.

#
Any::to

fn[T] Any::to(self : Any) -> T raise TypeMismatch

Extract the stored value as type T performing a runtime type id check.

Raises TypeMismatch if the ids differ. When T itself is Any, this will unwrap one level (special-case id) to avoid double wrapping.

On first mismatch for a type, its name is lazily registered for error output.

#
Any::try_to

fn[T] Any::try_to(self : Any) -> T?

Attempt to extract the stored value as type T returning None if the runtime type id does not match. This is the non-raising variant of to().

#
Any::type_id

fn Any::type_id(self : Any) -> TypeId

Returns the runtime type identifier for the value stored in this Any.

The TypeId is used internally to perform type checks during extraction. Two Any values will have physically equal type ids if and only if they contain values of the same type.

#
Any::type_info

fn Any::type_info(self : Any) -> TypeInfo

Returns the complete type information for the value stored in this Any.

The TypeInfo contains both the runtime type id and the human-readable type name. This is useful when you need both pieces of information or want to pass the complete type metadata around.

#
Any::type_name

fn Any::type_name(self : Any) -> String

Returns the human-readable name of the type stored in this Any.

This is particularly useful for debugging, logging, and error messages. The name is captured when the value is first wrapped with Any::of.

#
Any::unsafe_coerce

fn[T] Any::unsafe_coerce(self : Any) -> T

Unsafely coerce the underlying value to type T without checking.

Only use when you are certain of the actual stored type. Prefer to() or try_to() for safety. Incorrect use leads to undefined behavior.

#
TypeId

type TypeId

#
TypeInfo

type TypeInfo

#
TypeInfo::id

fn TypeInfo::id(self : TypeInfo) -> TypeId

Extracts the runtime type identifier from this TypeInfo.

Returns the TypeId component which is used for physical equality checks during type matching operations.

#
TypeInfo::name

fn TypeInfo::name(self : TypeInfo) -> String

Extracts the human-readable type name from this TypeInfo.

Returns the string representation of the type, useful for debugging, logging, and generating user-friendly error messages.

Source Files

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io