Why the language kernel is small
The handl language kernel has six primitives:
| Primitive | What it does |
|---|---|
$: | Declare schema for data |
expr: | Compute a field's value |
arg: | Feed a field's data value into its expr: |
merge: true | Spread computed object into parent scope |
${...} | Interpolate paths and function calls |
handle(body) { ... } | Intercept operations with handler clauses |
The language's expressive power comes from composition of these primitives, not from adding keywords. Patterns that would require dedicated syntax in other languages emerge naturally from field declarations and host-provided functions.
The reference runtime provides these host primitives:
| Host primitive | What it does |
|---|---|
import(path) | Load an external document |
apply(fn, ...) | Apply a first-class function value |
if(cond, then, else) | Conditional evaluation (lazy) |
and(a, b) / or(a, b) | Short-circuit logic (lazy) |
These are runtime host conventions, not language-level features. A runtime host may provide different functions or none at all.
The import field
The language describes its own module system using its own field syntax. Four primitives compose to produce an import field — no import keyword needed:
$:
import:
type: object
arg: { type: string }
expr: "import(arg)"
merge: true
host:
type: string
port:
type: number
import: "./base.yml"How it works:
arg: { type: string }— the field's data value ("./base.yml") is the input, type-checkedexpr: "import(arg)"— call theimporthost function on that pathtype: object— the output must be an object (required by merge)merge: true— spread the object into the parent scope, remove theimportfield
The field name import is just a regular name — not a reserved keyword. The language bootstraps its own import system from four primitives + one host function.
The functions field
Functions are emergent too. A function value is just data — a JSON object with params, optional return schema, and a body expression string. A function is identified by the presence of a body string field. The apply host primitive evaluates a function value's body with bound arguments. No functions: keyword needed:
$:
functions:
type: object
result:
type: number
expr: "double(5)"
functions:
double:
params:
x: { type: number }
return: { type: number }
body: "mul(x, 2)"
# result → 10How it works:
functionsfield — a regular field holding an object whose values are function values (objects with abodyfield)double(5)— the evaluator resolvesdoublevia path lookup to a function value, then dispatches viaapplyapply(fn, 5)— the host primitive bindsx = 5and evaluates the body"mul(x, 2)"
The field name functions is just a regular name — not a reserved keyword. User-defined functions emerge from field declarations + the apply host primitive.
Other emergent patterns
configfield —arg: { type: object }+expr: "arg"+merge: true: spread a data object into scope without importing a file- Dynamic import — compute the import path at runtime:
expr: "import(concat(arg, env.ENV))"(see LANG.md §8.3) - Function libraries —
import()a document with function values in its data; the functions are accessible via path resolution (e.g.lib.functions.my_fn(arg)) - Control flow —
if,and,oras lazy host functions;map,filter,reduceas regular host functions - Generic functions —
typeParams+{ "type": "var", "name": "T" }in function schemas enable parametric polymorphism, typechecked via unification — no runtime impact, no new keywords (see LANG.md §9.6) - Error handling — operations signal via
handleexpressions; handlers intercept, resume, or discard - Async I/O —
AsyncFunctiontrait for non-blocking network/timer operations viaeval_async - Cross-machine resumability —
eval_resumable+ serializableContinuation+CacheHostfor server→client migration - Platform services —
fs.read,env.get,http.get,cmd.runviaModule+ModuleHostcomposition
Relation to the object/arrow kernel
This page describes the field kernel — the schema/expression surface a host evaluates. The normative semantic kernel is the object/arrow kernel (ADR-0106): one world of objects, where sym()/sym(A) mint nominal identity and arrows (A ~> B) are the only computational form. Under that design the apply-binds-arguments model shown here — a function value with a body string and bound parameters — is superseded by product projections ((x, y) ~> x) with no captured environment.
The principle
The kernel stays small. The library of patterns grows. Users can invent their own field conventions without touching the compiler. The language is its own metalanguage — it describes its own extensions using its own field declaration syntax. Control flow, async I/O, cross-machine resumability, and platform services all emerge from the host + kernel split.