Skip to main content

Evaluation order and special forms

What this concept is​

An evaluation contract says which subexpression runs, in which environment, and when an error stops further evaluation. Ordinary application and conditional selection are different rules. This lab evaluates a function first, validates it, evaluates its argument in the caller environment, then enters its captured environment.

Why it matters here​

Two implementations can return the same result on successful arithmetic while disagreeing about errors or nontermination. Tests with failing branches expose the difference. A language designer must specify that behavior rather than inherit it accidentally from the host.

Concrete example​

The AST ('if', ('lit', True), ('lit', 7), ('var', 'missing')) returns 7. Evaluating both branches would raise an unbound-name error. Conversely, calling a constant-returning function with an unbound argument still fails: this evaluator uses eager arguments. A lazy conditional does not make the whole language lazy.

Common confusion / misconception​

A host-language function receiving already evaluated operands cannot implement branch selection simply by choosing one value afterward. Evaluation has already happened. Also, Scheme and this teaching language need not promise the same operand order; this lab deliberately specifies left-to-right evaluation.

How to use it​

Write inference steps in plain English before code: evaluate test; require boolean; choose branch; evaluate that branch in the same environment. For let, evaluate initializer outside the new frame. Thus outer x=7 followed by let x=x+1 yields 8; recursive binding would need another rule.

Check yourself​

Which error wins when the function expression evaluates to 2 and the argument is unbound? This contract reports closure required without evaluating the argument. Why would moving the type check change observable behavior?

Mini drill or application​

Write paired tests for both conditional branches and an unused eager argument. Add a let shadowing initializer case and trace each visited expression. Compare your order with another language and name the specification you used.

Read this only if stuck​

Use SICP evaluator chapter for eval/apply separation, then return to this lab’s explicit syntax and order.