You can predict a program's result by following the order in which it changes values and calls procedures. Lispex evaluates a call's operator first, then its operands from left to right.
Follow a changing value
Paste this complete program into the Playground.
(define x 0)
(list (begin (set! x 1) x) (begin (set! x (+ x 1)) x))Result
(1 2)The first begin sets x to 1 and returns 1. The second reads that new value, adds one, stores 2, and returns it. The list procedure receives 1 followed by 2.
In a call, the operator is the expression that supplies the procedure. Here it is list. The operands are the expressions that supply its arguments, here the two begin expressions.
Follow each kind of expression
| Expression | Evaluation order | What happens to the results |
|---|---|---|
| procedure call | operator, then operands from left to right | after checking the argument count, call the procedure |
begin or a procedure body | expressions from left to right | discard earlier results and return the last result |
if | test, then the selected branch | return that branch's result |
and | operands from left to right | stop at #f, or return the last result |
or | operands from left to right | stop at the first result other than #f |
let | initializers from left to right, using names from the enclosing scope | bind the new names for the body |
let* | each initializer, then its binding | later initializers can use earlier new bindings |
letrec | allocate storage for all names, then evaluate and assign initializers from left to right | all names are visible during initialization |
An initializer supplies a name's starting value. Scope determines where a name is visible. The storage for one binding is called a cell. Reading a letrec cell before it has a value raises E321.
An error stops the current evaluation. The order above applies to the expressions reached before that error. Earlier begin and body expressions can return any number of values because their results are discarded.
See which expressions are skipped
Run this as a separate program.
(list (if #t 7 (/ 1 0))
(and #f (/ 1 0))
(or 0 (/ 1 0)))Result
(7 #f 0)None of the divisions runs. The if selects 7, and stops at #f, and or stops at 0. Only #f is false, so 0 counts as true.
Argument checks happen after evaluation
An argument-count error does not prevent the operator and operands from running. With the Native CLI installed, save this complete file as arity-effects.lspx.
((begin (display "operator ") (lambda (x) x))
(begin (display "left ") 1)
(begin (display "right") 2))lispex run arity-effects.lspxStandard output contains the following text, with no final newline:
operator left rightThe procedure expects one argument and receives two. The command then writes this diagnostic to standard error:
E302 arity-effects.lspx:1:1 arity mismatch: expected 1 argument(s), got 2The exit code is 1, and no files are created. The procedure body does not run.
Bindings and scope explains the differences between the let forms. Conditionals covers branch selection.