Model of evaluation
What happens between the text of a program and its result, in four stages: read, expand, compile, evaluate. Everything else in the guide is a closer look at one of them.
Forms
A form is one complete piece of Koskari. The everyday shapes:
42 ;; a value
message ;; a name
(+ 1 2) ;; a call
(print "area:" (* 6 7)) ;; a call with a call inside it
A value evaluates to itself. A name evaluates to what it is bound to. A
call evaluates its arguments, then applies the thing in first position
to them. Inner calls finish before the call around them, so
(print "area:" (* 6 7)) prints area: 42.
A file is a sequence of forms, run from top to bottom:
(var width 6)
(var height 7)
(print "area:" (* width height))
Layout is another spelling of the same forms. A line with more than one
element is a call, and a line ending in : opens a block whose indented
lines are arguments to it:
var width 6
if (> width 5):
print "wide"
else:
print "narrow"
Nothing after the reader can tell which spelling was used. The rules, each beside the s-expression it produces, are in Koskari and s-expressions.
Read
The reader turns text into forms: atoms, pairs, and the collection
literals. (+ 1 2) becomes a pair chain whose head is the symbol +.
"hello" becomes a string and 42 an integer, since those need no
further work. Layout blocks are read into the same pair chains through
the layout handlers, so a block if and a written (cond ...) arrive at
the next stage as the same thing.
Reading is strict. A missing ) or a bad indent is a ReaderSyntaxError,
raised before anything runs. The reader also carries source positions,
which is what puts a Koskari line in a traceback.
Expand
The compiler walks each form. When a form’s head names a macro, the
macro receives the form and returns a new one, and the walk continues
on the result. if is a macro over cond; when, unless, set!,
class, and the comprehension forms each, list-each, and
dict-each are macros too:
>>> (macroexpand '(when ready (print "go")))
(cond [ready (print "go")] [else: None])
for-each is a compiler special rather than a macro; the comprehension
macros expand into it, and it expands no further.
Expansion is where a program’s vocabulary grows. A syntax in
your own module adds a head the walk knows about, and its uses lower to
ordinary forms before compilation. See
Macros and hygiene.
Compile
Once a form has no macro left in it, the compiler emits bytecode for it.
The heads it handles itself are the special forms: var,
lambda, cond, try, while, the operators, the imports, and about
a hundred more, each with a page in the
reference. Anything else in head
position is a call of a runtime value.
The picture that explains most behavior is a stack. For (+ 1 2) the
compiler emits: push the constant 1, push the constant 2, add two
operands. For (print (+ 1 2)): look up print, then the three
instructions above, then call with one argument. The inner call leaves
its value on the stack where the outer call finds it, which is why
evaluation is inside out.
Compile errors are shape errors: a var with no name, a lambda
whose formals are not a list, a form that matches none of a special’s
patterns. They are raised as CompilerError before the module runs.
(var)
reads fine and fails here.
Evaluate
The evaluator runs the bytecode. It is stackless: a Koskari call does not consume the C stack, so tail calls loop, deep recursion does not overflow, and a running function can be suspended and resumed. That last property is what makes every function able to await without being marked async; see Demand, async, and defer.
Names are looked up in the namespace the code was compiled in, then its
parents, then the module’s builtins. A name with no binding is a
NameError at the point of use, not at compile time, because a later
form or another module may bind it first. See
Name resolution and prefixes for how
(+ 1 2) becomes an opcode while + is also a function you can pass
around.
Runtime errors are Python exceptions. (/ 1 0) raises
ZeroDivisionError from the same place Python would.
Which stage failed
When a program surprises you, the traceback names the stage:
Error |
Stage |
What to look at |
|---|---|---|
|
read |
punctuation, indentation, a |
|
expand |
the form against the macro’s patterns; |
|
compile |
the form named in the message against its reference page |
|
evaluate |
the value, the missing name, the failing call |
The exception classes are catalogued in the Error guide.
Data and code
A form is also a value. Quote it and it stays a pair chain instead of running:
>>> '(+ 1 2)
(+ 1 2)
>>> (car '(+ 1 2))
+
That is the property macros rest on. A macro is a function whose input
and output are forms, and car, cdr, and cons are how it takes them
apart and puts them together. Data structures
covers the pair and the quoting forms.