any

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

    Download zip
    Version
    0.1.5
    License
    Apache-2.0
    Last updated
    10 months ago
    Downloads
    43K

    #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