Language guide
A tour with a small example at every step. The REPL, scripts, and packages load
library forms such as lambda and let automatically.
1. Values & quotes
Numbers, booleans, strings, and #inert are self-evaluating. Symbols look up bindings. Quote freezes a tree.
Literals
42
#t
"hello"
#inert
'foo
'(1 2 3)
2. define & if
define and if are primitive operatives: they control evaluation of their operands.
Bind and branch
(define answer 42)
(if (< answer 100)
'small
'huge)
3. Lists & vectors
Pairs use cons/car/cdr. Improper lists write (a & b). Vectors use […] or vector.
Structure
(cons 1 (cons 2 ()))
(car '(a b c))
(define v (vector 10 20 30))
(vector-ref v 1)
(vector-set! v 1 99)
(vector-ref v 1)
4. vau & lambda
vau builds operatives. lambda (stdlib) wraps a vau so arguments
evaluate first — the usual function calling convention.
Operative vs applicative
(define raw (vau (x) _ x))
(raw (+ 1 2)) ; ⇒ (+ 1 2)
(define add (lambda (x y) (+ x y)))
(add 3 4) ; ⇒ 7
((λ (x) (* x x)) 8) ; ⇒ 64
eval in the caller’s environment
(define force-it (vau (x) e (eval x e)))
(define n 21)
(force-it (+ n n))
; ⇒ 42
5. Control forms from the library
cond, and?, and or? are operatives — they short-circuit safely.
Short-circuit
(and? #f (/ 1 0)) ; ⇒ #f (no division)
(or? #t (/ 1 0)) ; ⇒ #t
(cond
((<= 1 0) 'nope)
((eqv? 1 1) 'yes))
6. The let family
let / let* / letrec
(let ((x 2) (y 3)) (* x y))
; ⇒ 6
(let* ((x 3) (y x)) (+ x y))
; ⇒ 6
(letrec ((sum (lambda (n)
(if (zero? n) 0 (+ n (sum (- n 1)))))))
(sum 5))
; ⇒ 15
7. First-class environments
Bundle bindings, evaluate remotely, or import names into the current environment.
bindings→environment & remote-eval
(define e (bindings->environment (x 10) (y 20)))
(remote-eval x e)
; ⇒ 10
(import! e x)
x
; ⇒ 10
8. Continuations
Full call/cc and delimited shift/reset are native.
Escape with call/cc
(call/cc (lambda (k) (* 5 (k 4))))
; ⇒ 4
Generator-style yield
(defn (yield x)
(shift (lambda (k) (cons x (k (#inert))))))
(reset (begin (yield 1) (yield 2) (yield 3) ()))
; ⇒ (1 2 3)
9. Tagged handlers & async
Prompt tags are unforgeable runtime values. prompt installs a deep effect
handler; perform delivers an operation and a one-shot resumption.
Untagged reset/shift remain multi-shot and backward compatible.
Deep handler with resume
(define request (make-prompt-tag))
(prompt request
(lambda (value k) (resume k (+ value 1)))
(+ 1 (perform request 40)))
; ⇒ 42
Await a CLR Task
(await-task (task-delay 25 "ready"))
; ⇒ "ready"
task-delay and await-task require the unrestricted profile.
Task callbacks only publish an outcome; the trampoline resumes evaluation serially.
10. Operative contracts
Optional contracts document whether a combiner sees raw syntax or evaluated values, then check operand and result shapes at the call boundary. User contracts are asserted metadata — never executed by the compiler.
Applicative and operative contracts
(define double (lambda (x) (+ x x)))
(contract double applicative (number) number pure #t)
(define raw (vau operands _ operands))
(contract raw operative (any) any pure #t)
(double 21) ; ⇒ 42
(raw (+ 1 2)) ; ⇒ (+ 1 2)
Shapes include any, number, integer, string,
boolean, atom, list, prompt-tag, and
resumption. Certified pure primitives such as (+ 20 22) may fold
behind a guarded fingerprint; rebinding the operator restores generic combination.
11. .NET interop
new, . (methods), .get / .set (fields & properties).
Static members use a type name atom; instance members use an object value.
Under the unrestricted profile these use reflection; the safe profile
exposes reviewed generated wrappers instead.
Open namespaces with clr-open, alias types with clr-alias, or capture a
first-class type with clr-type. Clojure-style calls also work when the head atom is
unbound: Type/Method, Type., .method, .-field.
CLR objects (unrestricted)
(clr-open System System.IO)
(define id (Guid/NewGuid))
(Console/WriteLine (String/Format "id={0}" id))
(define path (Path/Combine (Path/GetTempPath) "hello.txt"))
(File/WriteAllText path "IronKernel")
(File/ReadAllText path)
Aliases and first-class types
(clr-alias SB System.Text.StringBuilder)
(define sb (SB.))
(.Append sb "hi")
(.-Length sb)
; ⇒ 2
(define ConsoleT (clr-type System.Console))
(. ConsoleT WriteLine "from a Type value")
Generated safe bindings
(define greeting (String.concat "Hello from " "safe IronKernel!"))
(Console.write-line greeting)
(Math.sqrt 81)
; ⇒ 9
12. Capability profiles
Root environments carry a host-authority set. Authority is not a Kernel value — copying a binding cannot grant host access.
minimal— Kernel evaluation and data primitives onlysafe— minimal plus reviewed generated CLR wrappersunrestricted— raw reflection, source loading, and host I/O (default)
Run under the safe profile
ik --profile safe safe-clr.ikr
13. Projects & packages
.ikproj files are MSBuild-compatible IronKernel projects. The ik tool
creates apps, restores NuGet dependencies (IronKernel source packages and CLR libraries),
runs tests, and packs for publish. Install ik (or a release
IronKernel binary) as described in
Getting started — do not rely on
dotnet run --project IronKernel for ordinary project work.
Project workflow
ik new app hello
cd hello
ik run
ik test
ik add Acme.IronKernel.Http 1.2.0
ik add Npgsql 9.0.0 --clr
ik restore
ik tree
ik pack
Commit packages.lock.json and use ik restore --locked in CI.
Restored package sources under ironkernel/src/**/*.ikr load before project source.
14. Invent your own syntax
This is Kernel’s punchline: grow the language with vau.
trace — print source, then run it
(define trace
(vau (exp) env
(begin
(show exp)
(. System.Console WriteLine "")
(eval exp env))))
(trace (+ 2 2))
; prints: (+ 2 2)
; ⇒ 4
timed — no thunks required
(define timed
(vau (label exp) env
(let* ((start (.get System.DateTime Now))
(result (eval exp env))
(ms (.get (- (.get System.DateTime Now) start)
TotalMilliseconds)))
(printf "[{0}] {1} ms\n" label ms)
result)))
(timed "work" (letrec ((f (lambda (n)
(if (zero? n) 0 (+ n (f (- n 1)))))))
(f 200)))
See the full sample in
Examples/vau-dotnet.ikr,
then keep operators.html open as a reference.