Effects
The floor has four effectful primitives: print, read, read-file,
and log. All I/O goes through a single boundary — the evaluator owns
an I/O implementation, which is a terminal at the REPL and a script
harness in tests and in this book’s build. Nothing else in the
language touches the world.
(print e) writes the value of e and returns nil:
(print (cons 1 (cons 2 nil)))
;; prints: (1 2)
;=> nil
Because the result is nil, print sits in the effect slot of a
sequencing let rather than in the middle of an expression — for the
latter, log below returns its value.
read
(read prompt) prints the prompt, reads one line from the evaluator’s
input, and parses it as a datum — a value, not an expression: no
evaluation, no name resolution. End of input and blank lines read as
nil. The book’s build harness supplies no input, so here it reaches
end-of-input at once:
(read 'ready?)
;=> nil
Under the Purple session this primitive is the REPL: the read–eval–
print loop at the top of the tower is a floor program calling read
(Part IV).
read-file
(read-file 'path) reads a source file and parses it into a list of
forms-as-data — again values, not expressions, one list element per
top-level form. The path is a symbol, resolved relative to the process
working directory (with $NARJU_LIB as a fallback root, so installed
builds find their libraries from anywhere):
(car (car (read-file 'lib/prelude.naj)))
;=> 'define
The first form in the prelude is a define, and its head arrives as
the symbol define — plain data. define means nothing to the floor;
it is the Purple session that reads this file and decides what to do
with each form. read-file is the floor’s entire filesystem: the
Pink-level load, the tower boot, and the prelude all come through
it.
Because the file is source, the read-time transforms from earlier
chapters apply: the cadr family is desugared and quasiquote is
expanded before the data reaches the program.
log
(log b v) is a stage-aware debug print: with b a plain value it
prints v tagged [log] and — unlike print — returns it, so it can
wrap any subexpression without changing the program’s result:
(log 0 'checkpoint)
;; prints: [log] 'checkpoint
;=> 'checkpoint
The first operand is a stage dispatch, a pattern that recurs
throughout the staging forms: when b is code, the log is not
performed now but residualized — emitted into the program being
generated, to run when that program runs. Part II gives this pattern
its proper treatment; log is worth meeting early because it is the
tool for watching staged programs execute.