I have been impressed by Kernel, created by John Nathan Shutt, for several years. I tried to let it go, but it came back into my life, much like Python, JavaScript, or PHP, but without the same elegance or attitude. Perhaps it is elitism, or perhaps it is intellectual curiosity and possibly tribalism. Maybe it is the parentheses. I am repeating myself.
Kernel bridges almost 70 years of history. Lisp started in 1958 at MIT, Scheme branched off in 1975, Brian Cantwell Smith's 1982 thesis on 3-Lisp is the intellectual ancestor of everything Kernel does with reified environments, and Common Lisp crystallized another branch in the Symbolics era. The ideas never stopped spreading — Femtolisp powers Julia's parser, Clojure's edn brought s-expression data notation to a mainstream audience, Fennel found traction in game development. What these share is the recurring discovery that a small, programmable core pays off. Kernel's vau is the next idea in that lineage waiting to land: the unification of macros and procedures.
John Nathan Shutt managed to crystallize with brilliance and likely formal accuracy the work of the entire Lisp community since its early days with the release of the SINK interpreter and the last draft of specification for Kernel programming language in particular Revised -1 Report on the Kernel Programming Language in 2009. He supported his thesis Fexprs as the basis of Lisp function application or $vau : the ultimate abstraction in 2010. He passed away in 2020.
He was not the only one to believe in the ideas that are in Kernel. For example, first-class environments are found in MIT Scheme and Guile. I think I remember that Gambit has reified continuations (and more), and continuation attachments came to Chez Scheme, a device similar to keyed dynamic variables from Kernel. Gambit (again!) has had them under a different guise for much longer. So, Kernel, like any other Lisp, is a Lisp.
The thing that got me hooked on Kernel, despite my passion for code, was laziness. Despite the excellent work to document syntax rules and syntax-case such as the work of “Extending a Language — Writing Powerful Macros in Scheme” by Marc Nieper-Wißkirchen, I do not subscribe to this approach. I prefer Kernel’s vau. I like the idea of unifying macros and procedures so you don't need two separate metalinguistic systems, one mechanism instead of two.
However, vau was cursed. In 1998, Wand, "The Theory of Fexprs is Trivial," ACM SIGPLAN Notices 33(9), 1998. proved that fexpr make equational reasoning impossible because you cannot substitute equals for equals when you don’t know if an expression will be evaluated. Compilation was considered intractable.
Until 2026.
EDIT (2026-02-06): I was pointed to kraken-lang.org and in particular
Practical compilation of fexprs using partial evaluation: Fexprs can
performantly replace macros in purely-functional
Lisp. The difference between Kraken
and seed, is that seed is not purely functional, seed support
define, and set! but not on dynamic environments, also seed is
an extension of chezscheme.
Making vau’s dynamic environment immutable restores enough static knowledge to compile competitively. In standard Kernel, a vau operative receives the caller's dynamic environment as first-class value; it can read it, traverse it, and crucially, mutate it. That last power is what kills compilation. If any operative can rewrite the caller's bindings at any time, the compiler cannot know what any variable means at any call site, so it cannot substitute, inline, or optimize anything. The fix is surgical: pass the environment, but make it read-only. The operative can still introspect the caller's bindings, that is the whole point of vau, the ability to decide whether and how to evaluate its arguments, but it cannot side-effect them. In practice, it can't define, or set! a variable in the dynamic environment, hence there is no mutation, no new bindings. Concretely, in the compiler, each operative call passes the environment as the first element of a (env . args) pair. The environment is a value, not a mutable store. That single constraint — immutability — gives the compiler back enough static knowledge to reason about the code, inline calls, and emit the same quality of native code that Chez Scheme produces from syntax-case.
Catamorphism vs. syntax-case. Consider a small expression evaluator. In Scheme with syntax-case, you write a macro that pattern-matches at compile time, then a separate runtime function that walks the tree with car, cdr, and cond. Two mechanisms, two languages — the macro language and the runtime language — kept apart by design. In Kernel, vau with match does both in one shot. The match clause (+ ,a ,b) binds a and b and recurses when the comma signals a catamorphism, meaning the transformation applies to subexpressions automatically before the clause body sees them. No explicit recursive calls, no peeling apart list structure by hand. The Scheme version is more verbose not because the programmer is less skilled, but because the language forces the separation of two concerns that are, structurally, the same concern: tree-in, tree-out transformation. Readability is subjective; the reader can judge for themselves. Performance is not.
vau does not require learning a new DSL, the pattern matching domain specific language of syntax-rules, and the shenanigans of syntax-object-fu of syntax-case. All three implementation implement the same behavior, only the last, using vau is economical:
;;; myor — three implementations of short-circuit OR
;;;
;;; The point: vau controls evaluation directly.
;;; syntax-rules and syntax-case generate code that controls evaluation.
;;; Same result. One is a program. The other two are programs that write programs.
;;; -------------------------------------------------------
;;; 1. syntax-rules (R5RS / R7RS)
;;;
;;; The macro language: pattern templates with ellipsis.
;;; Short-circuit requires a let-binding to avoid double
;;; evaluation — the template language has no way to
;;; "evaluate once and test," so it must generate code
;;; that does it at runtime.
;;; -------------------------------------------------------
(define-syntax myor
(syntax-rules ()
((_) #f)
((_ e) e)
((_ e1 e2 ...)
(let ((t e1))
(if t t (myor e2 ...))))))
;;; -------------------------------------------------------
;;; 2. syntax-case (R6RS / Chez Scheme)
;;;
;;; More power: you can run arbitrary Scheme at expand time.
;;; But for this example, the extra power buys nothing —
;;; the structure is identical to syntax-rules. The reader
;;; still operates in two languages: the template language
;;; (#' quotes, ellipsis) and the runtime language.
;;; -------------------------------------------------------
(define-syntax myor
(lambda (x)
(syntax-case x ()
((_) #'#f)
((_ e) #'e)
((_ e1 e2 ...)
#'(let ((t e1))
(if t t (myor e2 ...)))))))
;;; -------------------------------------------------------
;;; 3. vau (Kernel / Seed)
;;;
;;; No template language. No phase separation. No ellipsis.
;;; The operative receives its arguments unevaluated and
;;; the caller's environment. It decides what to evaluate,
;;; when, and how many times — using ordinary code.
;;;
;;; The let-binding that syntax-rules must generate?
;;; Here it is just... a let-binding. Written by the
;;; programmer, not generated by a macro expander.
;;; -------------------------------------------------------
(define myor
(vau args env
(if (null? args) #f
(let ((v (eval (car args) env)))
(if v v
(eval (cons myor (cdr args)) env))))))
;;; -------------------------------------------------------
;;; Usage — identical in all three:
;;;
;;; (myor #f #f 42) => 42
;;; (myor #f #f #f) => #f
;;; (myor 1 (error "!")) => 1 (second arg never evaluated)
;;; -------------------------------------------------------# make-benchmark.sh
── N-Queens — exercising single CPU ──────────────────────────────
scheme --script seedink.scm n-queen.seed compile: n/as execute: 22.988239534s wall: 23.598s
scheme --script n-queen.scm execute: 23.003s wall: 23.398s
── Collatz — exercising syntax-rules ────────────────────────────
scheme --script seedink.scm collatz.seed compile: n/as execute: 9.995808085s wall: 10.263s
scheme --script collatz.scm execute: 10.307s wall: 10.342s
── Abacus — exercising syntax-case ───────────────────────────────
scheme --script seedink.scm abacus.seed compile: n/as tree: 1.174771122s eval: 1.649855682s wall: 3.356s
scheme --script abacus.scm tree: 1.259s eval: 1.560s wall: 3.108s
── Abacus2 — multi-operand match (ternary trees) ─────────────────
scheme --script seedink.scm abacus2.seed compile: n/as tree: 0.803791520s eval: 3.897080440s wall: 5.337s
scheme --script abacus2.scm tree: 0.810s eval: 5.441s wall: 6.879s
── Summary ────────────────────────────────────────────────────────
Benchmark Driver Monotonic Wall Clock
──────────────────── ─────────────── ──────────── ────────────
N-Queens seedink 22.988s 23.598s
N-Queens scheme 23.003s 23.398s
Collatz seedink 9.996s 10.263s
Collatz scheme 10.307s 10.342s
Abacus seedink 2.825s 3.356s
Abacus scheme 2.819s 3.108s
Abacus2 seedink 4.701s 5.337s
Abacus2 scheme 6.251s 6.879s
──────────────────── ─────────────── ──────────── ────────────
TOTAL seedink 40.510s 42.554s
TOTAL scheme 42.381s 43.727sHere is the Chez Scheme code. Benchmark! Enjoy! And let there be… evaluation.
The implementation was developed with Claude.AI as a coding assistant. I directed the design and reviewed the code. This is exploratory work — a proof of concept for intellectual curiosity, not production infrastructure.
Claude is AI and I can make mistakes. Please double-check anything.