Expressions and Evaluation Order

Predict changes to values, output, and errors by following procedure calls and conditional expressions in order.

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.

LISPEX
(define x 0)
(list (begin (set! x 1) x) (begin (set! x (+ x 1)) x))

Result

OUTPUT
(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

ExpressionEvaluation orderWhat happens to the results
procedure calloperator, then operands from left to rightafter checking the argument count, call the procedure
begin or a procedure bodyexpressions from left to rightdiscard earlier results and return the last result
iftest, then the selected branchreturn that branch's result
andoperands from left to rightstop at #f, or return the last result
oroperands from left to rightstop at the first result other than #f
letinitializers from left to right, using names from the enclosing scopebind the new names for the body
let*each initializer, then its bindinglater initializers can use earlier new bindings
letrecallocate storage for all names, then evaluate and assign initializers from left to rightall 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.

LISPEX
(list (if #t 7 (/ 1 0))
      (and #f (/ 1 0))
      (or 0 (/ 1 0)))

Result

OUTPUT
(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.

LISPEX
((begin (display "operator ") (lambda (x) x))
 (begin (display "left ") 1)
 (begin (display "right") 2))
SH
lispex run arity-effects.lspx

Standard output contains the following text, with no final newline:

operator left right

The 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 2

The 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.