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
@structdefinitions. - 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
:docsis always present (empty string if none).:tagsis always present (empty map if none).:detailsis 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.)
- Type hint tags (
- Always includes
:tags(empty map if none). Tag keys are strings like"@str"and values are parameter lists. - Includes
:hasDefaultindicating whether a default value exists. - Includes
:vargsindicating 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
@structdefinitions. - 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.