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)
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)
small

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)
(1 2) · a · 20 · #inert · 99

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))
#f · #t · 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.

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.