Metadata¶
Metadata is a map of data attached to vars, providing information about definitions without affecting their values. Metadata enables documentation, compiler hints, and runtime introspection.
Attaching Metadata¶
Metadata is attached using the ^ reader macro:
;; Full map syntax
(def ^%{:doc "The ratio of circumference to diameter" :added "1.0"} pi 3.14159)
;; Shorthand for boolean true
(def ^:private helper-fn (fn* [x] ...))
;; Multiple shorthands
(def ^:private ^:process-bound *counter* 0)
;; Combined
(def ^:private ^%{:doc "Internal counter"} *counter* 0)
Shorthand expansion:
;; @todo
(def ^:private secret 42)
(:private (meta #'secret)) ; => true
(def ^%{:doc "Documented" :added "1.0"} api-fn (fn* [] :ok))
(:doc (meta #'api-fn)) ; => "Documented"
(:added (meta #'api-fn)) ; => "1.0"
Keyword shorthand expands to map:
;; @todo ^:keyword expands to ^%{:keyword true}
(def ^:dynamic ^:private dyn-var 1)
(:dynamic (meta #'dyn-var)) ; => true
(:private (meta #'dyn-var)) ; => true
Accessing Metadata¶
;; @todo
(def ^%{:doc "A documented value"} documented-var 42)
(map? (meta #'documented-var)) ; => true
(:doc (meta #'documented-var)) ; => "A documented value"
(symbol? (:name (meta #'documented-var))) ; => true
meta returns nil for values without metadata:
System-Recognized Keys¶
The following metadata keys have special meaning to the compiler and runtime.
Definition Type¶
| Key | Type | Description |
|---|---|---|
:native |
boolean | Var is bound to a VM intrinsic. The intrinsic must exist in the VM's registry. |
:special-form |
boolean | Var is a special form (implies :native). Cannot be used as a first-class value. |
:macro |
boolean | Var is a macro. Function is invoked at compile-time with unevaluated forms. |
Documentation¶
| Key | Type | Description |
|---|---|---|
:doc |
string | Documentation string (1-3 lines recommended). |
:arglists |
list | List of argument vectors, one per arity. Example: ([x] [x y] [x y & more]) |
Visibility¶
| Key | Type | Description |
|---|---|---|
:private |
boolean | Var is implementation detail. Not exported by refer. |
Binding Behavior¶
| Key | Type | Description |
|---|---|---|
:process-bound |
boolean | Each process has its own value. Inherited from parent at spawn time. |
Compiler-Added¶
These keys are added automatically by the compiler:
| Key | Type | Description |
|---|---|---|
:name |
symbol | The simple name of the var. |
:ns |
namespace | The namespace containing the var. |
:file |
string | Source file path. |
:line |
integer | Line number in source file. |
;; @todo
(def compiler-test 42)
(symbol? (:name (meta #'compiler-test))) ; => true
(= (:name (meta #'compiler-test)) 'compiler-test) ; => true
(namespace? (:ns (meta #'compiler-test))) ; => true
(or (nil? (:file (meta #'compiler-test)))
(string? (:file (meta #'compiler-test)))) ; => true
(or (nil? (:line (meta #'compiler-test)))
(integer? (:line (meta #'compiler-test)))) ; => true
Native Intrinsics¶
The :native metadata connects a var to a VM intrinsic function:
(def ^%{:native true
:doc "Returns the sum of nums. (+) returns 0."
:arglists '([] [x] [x y] [x y & more])}
+)
Behavior:
- The VM looks up the symbol in its intrinsic registry
- If found, the var is bound to the intrinsic function
- If not found, an error is raised at load time
Validation: Declaring a native that doesn't exist in the VM is a load-time error. This ensures the Lonala source and VM implementation stay synchronized.
;; @todo
;; Defining a native that doesn't exist is a load-time error
(def ^:native nonexistent-intrinsic) ; => ERROR :unknown-intrinsic
Special Forms¶
Special forms are a subset of natives with :special-form true:
(def ^%{:native true
:special-form true
:doc "Creates or updates a var binding."
:arglists '([name] [name value])}
def)
Additional restrictions:
- Cannot be passed as values (e.g.,
(map if ...)is an error) - Evaluated by the compiler before var resolution
Bootstrap¶
The def special form is the only var seeded by the VM. All other natives and special forms are registered using def:
VM starts
↓
VM seeds: def
↓
(def ^:native ^:special-form fn* ...)
(def ^:native ^:special-form match ...)
(def ^:native ^:special-form do ...)
(def ^:native ^:special-form quote ...)
↓
(def ^:native + ...)
(def ^:native first ...)
... all other intrinsics ...
↓
(def ^%{:macro true} defmacro ...)
↓
Derived macros and functions
Macros¶
The :macro metadata marks a var as a macro:
(def ^%{:macro true
:doc "Evaluates test. If truthy, evaluates body."
:arglists '([test & body])}
when
(fn* [test & body]
`(match ~test
false nil
nil nil
_ (do ~@body))))
Behavior:
- At compile-time, when
whenappears in call position - The compiler invokes the function with unevaluated forms
- The returned form replaces the original in the AST
- Expansion continues recursively
Process-Bound Vars¶
The :process-bound metadata creates per-process bindings:
Behavior:
- A single Var exists in the realm with the
PROCESS_BOUNDflag - Each process can shadow the root value via a per-process binding table
defon a process-bound var updates only the current process's binding (not the realm root)- Spawned processes inherit parent's binding values at spawn time
Use cases:
*ns*— current namespace (each process can be in different namespace)- Dynamic context that shouldn't leak between processes
Private Vars¶
The :private metadata marks implementation details:
Behavior:
- Not included when using
(refer 'namespace) - Still accessible via fully-qualified name:
my.ns/parse-options - Signals intent, not enforcement
;; @todo
(def ns (create-ns 'private.test))
(intern ns (with-meta 'helper %{:private true}) (fn* [] :internal))
;; helper is not referred by (refer 'private.test)
;; but private.test/helper still works
;; Private vars are excluded from refer
(def test-ns (create-ns 'refer.test))
(intern test-ns (with-meta 'public-fn %{}) (fn* [] :public))
(intern test-ns (with-meta 'private-fn %{:private true}) (fn* [] :private))
;; After (refer 'refer.test), public-fn is accessible but private-fn is not
(refer 'refer.test)
(fn? public-fn) ; => true
Arglists Format¶
The :arglists key documents function signatures:
;; Single arity
:arglists '([coll])
;; Multiple arities
:arglists '([coll] [coll not-found])
;; Variadic
:arglists '([x] [x y] [x y & more])
;; Destructuring shown for documentation
:arglists '([{:keys [host port timeout]}])
Conventions:
- Each inner vector is one valid arity
- Parameter names should be descriptive
& restindicates variadic- Ordered from fewest to most arguments
Metadata Equality¶
Metadata does not affect equality:
(= (with-meta 'foo %{:a 1})
(with-meta 'foo %{:b 2})) ; => true @todo
(= (with-meta [1 2 3] %{:x 1})
(with-meta [1 2 3] %{:y 2})) ; => true
(= (with-meta 'foo %{:a 1})
'foo) ; => true @todo
Two values differing only in metadata are equal. This allows metadata to annotate without changing semantics.
with-meta preserves value, attaches metadata:
;; @todo
(def s (with-meta 'bar %{:tag :special}))
(= s 'bar) ; => true
(:tag (meta s)) ; => :special
Summary Table¶
| Key | Added By | Purpose |
|---|---|---|
:native |
User | Bind to VM intrinsic |
:special-form |
User | Mark as special form (implies :native) |
:macro |
User/defmacro |
Mark as compile-time macro |
:doc |
User | Documentation string |
:arglists |
User/Compiler | Argument signatures |
:private |
User/defn- |
Mark as internal |
:process-bound |
User | Per-process binding |
:name |
Compiler | Var's simple name |
:ns |
Compiler | Containing namespace |
:file |
Compiler | Source file |
:line |
Compiler | Source line |