Reflection & Eval

Nostos provides powerful metaprogramming capabilities including runtime type introspection, dynamic value construction, and code evaluation.

Type Introspection with typeInfo

The typeInfo(typeName) builtin returns type metadata as a Map, allowing you to inspect types at runtime.

type Person = { name: String, age: Int }
type Status = Active | Pending | Done

main() = {
    # Get record type info
    personInfo = typeInfo("Person")
    println(personInfo.get("kind"))    # "record"
    println(personInfo.get("fields"))  # List of field metadata

    # Get variant type info
    statusInfo = typeInfo("Status")
    println(statusInfo.get("kind"))    # "variant"
    println(statusInfo.get("constructors"))  # List of constructors

    # Unknown types return empty map
    unknownInfo = typeInfo("NoSuchType")
    println(unknownInfo.isEmpty())  # true
}

typeInfo Return Value

  • "name" - Type name as string
  • "kind" - "record" or "variant"
  • "fields" - List of Maps with "name" and "type" (for records)
  • "constructors" - List of Maps with constructor info (for variants)

Getting Variant Tags with tagOf

The tagOf(value) builtin returns the constructor name of a variant value, or an empty string for non-variants.

type Result = Ok(Int) | Err(String)
type Color = Red | Green | Blue

main() = {
    result = Ok(42)
    println(tagOf(result))  # "Ok"

    err = Err("failed")
    println(tagOf(err))     # "Err"

    color = Red
    println(tagOf(color))   # "Red"

    # Non-variants return empty string
    println(tagOf(42))      # ""
    println(tagOf("hello")) # ""
}

Dynamic Value Construction

Construct typed values dynamically using makeRecord and makeVariant.

With Type Parameters

Use type parameters when the type is known at compile time.

use stdlib.json.{String, Number}

type Person = { name: String, age: Int }
type Result = Ok(Int) | Err(String)

main() = {
    # Construct a record from a Map[String, Json]
    fields = %{"name": String("Alice"), "age": Number(30.0)}
    person = makeRecord[Person](fields)
    println(person.name)  # "Alice"
    println(person.age)   # 30

    # Construct a variant
    ok_fields = %{"_0": Number(42.0)}
    result = makeVariant[Result]("Ok", ok_fields)
    # result is Ok(42)
}

With String Type Names

Use string-based versions when the type name is determined at runtime.

use stdlib.json.{jsonParse, fromJsonValue, String, Number}

type Person = { name: String, age: Int }

main() = {
    # Type name as runtime string
    typeName = "Person"
    fields = %{"name": String("Bob"), "age": Number(25.0)}
    person = makeRecordByName(typeName, fields)

    # Same for variants
    result = makeVariantByName("Result", "Ok", %{"_0": Number(100.0)})

    # JSON to typed value using stdlib
    json = jsonParse('{"name": "Charlie", "age": 35}')
    person2 = fromJsonValue("Person", json)
}

Dynamic Code Evaluation

The eval(code) builtin compiles and executes Nostos code at runtime, returning the result as a string.

main() = {
    # Evaluate simple expressions
    result = eval("1 + 2 * 3")
    println(result)  # "7"

    # Define and call functions
    eval("add(a, b) = a + b")
    result2 = eval("add(10, 20)")
    println(result2)  # "30"

    # Works with any Nostos code
    eval("type Point = { x: Int, y: Int }")
    result3 = eval("Point(1, 2)")
    println(result3)  # "Point { x: 1, y: 2 }"
}

Performance Note

eval compiles code at runtime, which has overhead. For performance-critical code, prefer pre-compiled functions. Use eval for:

  • REPL-like interfaces
  • Dynamic configuration
  • Plugin systems
  • Prototyping and experimentation

Use Cases

  • Generic serialization: Build JSON/YAML/XML serializers that work with any type
  • ORM mapping: Map database rows to typed records dynamically
  • Form generation: Generate UI forms from type schemas
  • Plugin systems: Load and execute code at runtime
  • REPL tools: Build interactive development environments