The Purple Session
narju with no flags does not start the floor REPL. It boots the
Purple session: an interactive environment that is itself a program —
lib/purple.naj, one closed floor expression that constructs
everything in Parts I–III at startup and ends in a read-eval-print
loop. The purple blocks throughout this book are transcripts of that
loop. This chapter is about the session as a user sees it; the next
chapter opens the machinery.
Session state
A floor program is one closed expression; there is no floor-level
define. The session has one:
(define x 42)
x
;=> 42
(set! x 43)
;=> 'x
x
;=> 43
define binds a name for the rest of the session and prints nothing
on success. set! mutates an existing binding and returns the name.
Neither is a floor form — the session’s environment is a frame of
mutable cells, and define splices a new cell into it. The
primitives are bound without cells and cannot be reassigned:
(set! + 9)
;=> ('set-immutable . '+)
Two syntaxes
The session evaluator accepts the floor forms of Part I unchanged —
self-named unary lambda, one binder per let:
(let y 5 (+ y 1))
;=> 6
((lambda f n (if (eq? n 0) 1 (* n (f (- n 1))))) 5)
;=> 120
and alongside them the multi-parameter forms of the reference tower
(lms-black), plus begin:
((lambda (a b) (+ a b)) 1 2)
;=> 3
(let ((a 1) (b 2)) (+ a b))
;=> 3
(begin (define tries 0) (set! tries (+ tries 1)) tries)
;=> 1
The evaluator tells them apart by shape: a symbol in the parameter or binder position means the floor form, a list means the lms-black form. Both produce the same kind of closure.
Errors
The session’s base environment raises ('unbound . name) where
Pink’s nil-env answers a silent 0, so a typo is an error rather
than a wrong number. The loop prints each form’s result through
catch, so an error is a printed payload and the session continues:
zzz
;=> ('unbound . 'zzz)
(+ 1 2)
;=> 3
The prelude
lib/prelude.naj is loaded at boot: append, map, assoc,
length, reverse, all curried in the floor style (the Prelude
chapter in Part V has one entry per function):
((append '(1 2)) '(3 4))
;=> (1 2 3 4)
((map (lambda (n) (* n n))) '(1 2 3))
;=> (1 4 9)
(reverse '(1 2 3))
;=> (3 2 1)
load
(load lib/matcher.naj) evaluates a file’s forms into the session.
The path is a bare symbol, not quoted — the loop treats load and
define as commands, taking their arguments as unevaluated data and
printing nothing on success. The matcher and µKanren chapters both
start this way.
What boots
lib/purple.naj is a let-chain. In order, it:
- reads
lib/pink-forms.najas data and animates it withtrans/evalms(the boot from the metacircular chapter), producingpink-evalandpink-evalc; - runs the second and third Futamura projections —
compilerandevalc-compiledare compiled at every boot, which is what the banner’s “running futamura projections” refers to; - binds the helpers of Part III:
compile,run-compiled,interpret,load-into; - reads
lib/tower.najas data and animates it the same way, then builds one meta level and one global environment from it — the session; - installs the Pink bindings of steps 1–3 into the session environment as cells (the full list is in Part V’s session-bindings chapter);
- loads the prelude and enters the loop.
The loop itself sits at the end of the file: read a datum;
load and define are handled as above; anything else is evaluated
through the tower and printed through catch. Programs,
interpreters, compilers, and the session that serves them are all
values built by one floor expression — the tower that expression
builds is next.