Invisible link to canonical for Microformats

ADR-028 slug.meta.describe Returns Symbol-Keyed Map


Status

Accepted

Context

We want to enhance the slug.meta library with a describe(value) function that returns structured metadata about any runtime value.

Primary goals:

  • Enable runtime introspection for debugging, REPL usage, doc generation, and AI tooling.
  • Support programmatic inspection of modules, e.g.:
var module = import("slug.sys")

module
/> keys
/> reduce({}, fn(acc,k) {
  acc /> put(k, module[k] /> describe)
})
/> println
  • Keep runtime implementation simple.
  • Avoid tight coupling between runtime and @struct definitions.
  • Preserve Slug’s immutability and explicit semantics.
  • Provide a single unified introspection entrypoint.

Previously considered designs returned strongly-typed structs. However, this increases runtime complexity and version coupling.

Decision

slug.meta.describe(value) will return a map keyed by symbols, not a struct.

The runtime will generate this map directly.

If stricter typing is desired, slug.meta may provide struct wrappers that convert the returned map into a defined @struct.

API

var { describe } = import("slug.meta")

val info = describe(value)

Return Type

describe(value) -> map

The returned map will always include the following keys:

Key Type Description
:type symbol Runtime type of the value
:docs str Documentation string attached to the value (empty if none)
:tags map Tags attached to the value (empty if none). Keys are tag name strings like "@export". Values are parameter lists.
:details map Type-specific metadata (may be empty)

Invariants

  • :docs is always present (empty string if none).
  • :tags is always present (empty map if none).
  • :details is always present (empty map if none).
  • The schema is additive only; required keys will not be removed in future versions.

Core Schema Example

{
  :type: :list,
  :docs: "",
  :tags: {},
  :details: {}
}

Type-Specific Details

Numbers (:num)

{
  :type: :num,
  :docs: "",
  :tags: {},
  :details: {}
}

Strings (:str)

{
  :type: :str,
  :docs: "",
  :tags: {},
  :details: {}
}

Lists (:list)

{
  :type: :list,
  :docs: "",
  :tags: {},
  :details: {}
}

Maps (:map)

{
  :type: :map,
  :docs: "",
  :tags: {},
  :details: {}
}

Struct Instances (:struct)

{
  :type: :struct,
  :docs: "",
  :tags: {},
  :details: {
    :structType: :User,
    :fields: [
      { :name: "name", :tags: {"@str": []}, :hasDefault: false }
    ],
    :fieldCount: 1
  }
}

Functions (:fn)

{
  :type: :fn,
  :docs: "Load SQL queries from a base directory.",
  :tags: {"@export": []},
  :details: {
    :foreign: false,
    :arityMin: 0,
    :arityMax: 1,
    :params: [
      { :name: "base", :tags: {"@str": []}, :hasDefault: true, :vargs: false }
    ]
  }
}

Parameter Metadata

Each parameter entry in :details[:params]:

  • Includes all tags attached to the parameter, including:

    • Type hint tags (@str, @num, @struct(User), etc.)
    • Semantic tags (@cfg, @secret, @path, etc.)
  • Always includes :tags (empty map if none). Tag keys are strings like "@str" and values are parameter lists.
  • Includes :hasDefault indicating whether a default value exists.
  • Includes :vargs indicating whether this parameter is the variadic parameter.

Parameter tags are considered canonical metadata for documentation and AI tooling.

Modules (:module)

{
  :type: :module,
  :docs: "",
  :tags: {},
  :details: {
    :exports: [:argv, :os, :env]
  }
}

Function Groups (:grp)

Function groups with multiple overloads return :type: :grp and provide a list of per-overload descriptions under :details[:groups]. Each entry mirrors the :fn schema. If a function group has a single overload, it is reported as :type: :fn with the standard function details (no :groups list).

{
  :type: :grp,
  :docs: "",
  :tags: {},
  :details: {
    :groups: [
      {
        :type: :fn,
        :docs: "x is cool",
        :tags: {},
        :details: {
          :foreign: false,
          :arityMin: 1,
          :arityMax: 9223372036854775000,
          :params: [
            { :name: "a", :tags: {"@num": []}, :hasDefault: false, :vargs: false },
            { :name: "args", :tags: {}, :hasDefault: false, :vargs: true }
          ]
        }
      }
    ]
  }
}

Removal of doc()

Upon adoption of meta.describe, the existing doc(value) function will be deprecated and removed in a future release.

Documentation access is unified under:

describe(value)[:docs]

This consolidates reflection and documentation retrieval into a single introspection primitive.

Non-Goals

  • Discovering all variable names bound to a given value (alias tracking).
  • Deep recursive introspection.
  • Mutation or reflection-based modification of values.
  • Guaranteeing stable identity across processes.

Runtime Implementation Strategy

The runtime will implement:

Describe(Value) -> MapValue

Using a type switch on the internal value tag.

The implementation must:

  • Avoid deep recursion.
  • Avoid walking entire large structures.
  • Produce deterministic key ordering where possible.
  • Preserve immutability of described values.

Optional Struct Mapping (Library-Side)

If stricter typing is desired:

@struct(Describe)
val type    :symbol
val docs    :str
val tags    :list
val details :map

@export
var asDescribe = fn(m) {
  Describe(
    type: m[:type],
    docs: m[:docs],
    tags: m[:tags],
    details: m[:details]
  )
}

This keeps the runtime simple while allowing typed wrappers at the language level.

Consequences

Positive

  • Very simple runtime implementation.
  • Stable and extensible.
  • Unified introspection entrypoint.
  • Ideal for debugging and REPL.
  • AI- and tooling-friendly.
  • No tight coupling between runtime and @struct definitions.
  • Cleanly replaces doc().

Negative

  • Weaker compile-time guarantees compared to returning a struct.
  • Consumers must trust documented keys.

Final Design Principle

describe(value) is:

  • Pure
  • Shallow
  • Deterministic
  • Additive-evolution friendly
  • Runtime-cheap
  • Tool-oriented

It provides semantic metadata for documentation and tooling without exposing mutable internal state.