sicp.io
Chapter 4 · Checkpoint

Read the syntax. Carry the context. Make control explicit.

Use twenty-three canonical lesson programs to reconnect expression data, lexical environments, evaluator records, source transformation, analyzed execution, strict and lazy strategies, nondeterministic continuations, frame streams, unification, rule search, and the exact input and resource boundary of every finite observation.

Guiding question

Can you explain how one represented program moves through syntax classification, environment lookup, procedure application, delayed demand, alternative continuations, and query-frame expansion without confusing the educational model with the shipped Lispex runtime?

  • Interpret a quoted expression tree as data
  • Bind the same symbol differently in different environments
  • Evaluate only the selected branch of a special form
  • Analyze expression structure once and reuse an execution plan
  • Transform let into an existing lambda application rule
  • Force an explicit thunk once and memoize its value
  • Retain every successful finite alternative
  • Extend a frame only with consistent pattern bindings
  • Carry frames through a finite sequence of rule goals
  • Report recursive search as complete or truncated with a visible frontier
  • Distinguish failed lookup from negation and declarative relation from operational search
  • Allocate internal names before assignments install mutually recursive procedure values
  • Run quoted top-level forms through eval/apply while definitions persist in one global environment
  • Thread success and failure continuations so require can resume an earlier amb choice
  • Separate syntax dispatch in eval from procedure dispatch in apply
  • Construct frames, environment chains, primitive records, compound procedures, and ordered argument lists
  • Generate, inspect, transform, and then evaluate program data
  • Compare applicative order with a non-memoized normal-order schedule
  • Delay compound-procedure arguments and memoize demanded values in a lazy evaluator
  • Build an unbounded lazy list inside the guest language and demand a finite prefix
  • Express finite puzzles as choices plus constraints and inspect solution order
  • Treat a query as a transformation from an input frame stream to an output frame stream
  • Rename rule variables, unify terms, and apply a visible rule-depth budget

The opening lessons establish the representation boundary. Expressions, environments, procedure records, and transformed source are ordinary data supplied to finite lesson evaluators. Each lesson names the syntax and data structures in its model.

The assembled eval/apply lessons connect those pieces: eval classifies source forms, apply distinguishes primitive from compound procedures, frames hold mutable bindings, closures retain lexical environments, and a finite driver preserves one guest global environment across top-level forms.

The evaluation-order sequence then changes the control protocol. A strict schedule computes operands before application. A normal-order model delays them without memoization. The lazy evaluator stores expression-environment thunks and replaces a demanded thunk with its value, while the lazy-list lesson defines ordinary guest procedures that behave like non-strict list constructors.

The nondeterministic sequence carries both success and failure continuations. Amb installs the remaining choices in the failure path, require invokes that path when a constraint fails, and the puzzle lesson uses the same evaluator protocol to enumerate finite solutions in a visible order.

The logic-programming sequence moves frames through assertions, conjunctions, disjunctions, and rules. The implementation lesson adds two-way unification and fresh rule-variable names, while explicit work and depth limits keep unfinished or cyclic searches from masquerading as complete answers.

Across all twenty-three programs, each fixed-limit execution trace records the exact events of one selected run through the educational evaluator and query systems.

SICP code519 of 1,048,576 UTF-8 bytes
Examples
Result
Output
Value
Diagnostic
Execution trace0 / 0 events
    Programs run in the browser with their result and execution trace.
    Expected result

    Each program returns the first expected observation from its corresponding Chapter 4 lesson. The finite driver returns (complete ((defined square) (defined make-adder) (defined add-five) (defined base) 49 12 (assigned base) 81 6) 0), and the amb evaluator returns (complete ((1 4) (2 3))) for the constrained pair search. The new lessons additionally expose evaluator records, generated program data, strict-versus-delayed work counts, memoized demand, finite lazy-list prefixes, ordered puzzle solutions, frame-stream transformations, and rule expansion under a work budget.

    Trace focus

    Follow expression traversal, nearest-frame lookup, branch selection, analysis construction, source transformation, primitive and compound application, argument-list order, thunk creation and memoization, finite lazy-list demand, amb choice installation, require failure, puzzle backtracking, frame extension, assertion scans, conjunction sequencing, rule-variable renaming, unification, and budget decrease. These observations remain scoped to the selected finite runs.

    Review

    Twenty-three checks for evaluators, delayed work, alternatives, and queries

    What must an evaluator do to turn expression data into a value?

    Answer Numbers evaluate directly. A compound expression asks evaluate to interpret both nested operands before combining their values. The evaluator follows the expression tree one node at a time.

    Why can the same expression produce a different value in another environment?

    Answer evaluate does not attach one permanent meaning to x or y. It receives an environment with the expression, so the same expression tree can be reused with different bindings.

    What would go wrong if an evaluator evaluated both branches of if?

    Answer In the first example the alternative divides by zero. The program still returns 60 because the true predicate selects the addition branch and the invalid alternative remains expression data.

    Which work can be done before the expression receives an environment?

    Answer The resulting plan accepts an environment. Running it only looks up variables, runs the stored operand plans, and applies the already selected operator. One analyzed plan can therefore serve many environments.

    What must remain unchanged when let is rewritten as a lambda application?

    Answer The evaluator does not need a second implementation of local binding. Its let case rewrites the expression and sends the result back through the ordinary lambda and application cases in the same environment.

    Which representation changes make call-by-need visible inside an evaluator?

    Answer force-it inspects the tag. On the first demand it calls the stored computation, changes the tag to evaluated-thunk, discards the computation, and stores the value. Later demands select the cached slot and perform no computation again. This exposes the representation change that a lazy evaluator normally hides behind argument handling.

    What changes when evaluation keeps the remaining alternatives after one success?

    Answer The second program makes two choice positions explicit. scan-y tests every y for one x, and scan-x repeats that work for every x. Returning all pairs whose squared components sum to 25 exposes a finite nondeterministic search as ordinary list-producing control.

    How can a matcher carry partial knowledge while it walks two data structures?

    Answer A later occurrence looks up the existing binding before extending anything. Equal data preserves the frame; conflicting data returns failed, which every remaining recursive step propagates. This is one-way matching with variables in the pattern, not full bidirectional unification or database search.

    How does a rule preserve the middle binding needed by its next goal?

    Answer The second program binds grand from the rule head, then solve-goals processes the two parent goals in order. The first goal produces middle values ben and dia. Each frame becomes input to the second goal, which finds cy and eli. The lesson evaluator processes direct parent facts and one ordered parent-rule body while preserving every intermediate frame.

    How can recursive rule expansion terminate honestly when the data may contain a cycle?

    Answer The second program uses a three-person cycle. Expanding ada reaches ben, ben reaches cy, and cy reaches ada again. The evaluator spends exactly one work unit per removed frontier item and returns truncated with the remaining frontier when the budget reaches zero. This breadth-first lesson model covers parent expansion, an explicit frontier, repeated answers, and complete or truncated status.

    Which conclusions come from the logical relation, and which come from the particular database and search procedure?

    Answer The second program gives married a symmetric operational rule: when a direct fact is absent, swap the arguments and search again. This proves (married mickey minnie) after one swap because the reverse fact exists. The same rule alternates forever for an unrelated pair unless the evaluator detects repetition or spends a visible work budget. The logical statement may be symmetric, but the direction and control of the rule still determine the behavior of this executable search.

    Why must an evaluator create all internal bindings before it installs any of the procedure values?

    Answer The second program executes both forms. classify-original uses internal definitions directly. classify-scanned writes the transformed let and set! structure explicitly. Both return the same parity observations for 7 and 8, and equal? reports true. The finite comparison checks this transformation and makes its explicit intermediate structure visible.

    What turns a collection of evaluator procedures into a program that can run a sequence of user forms?

    Answer run-program is the driver. It receives quoted top-level forms, one explicit global environment, and a form budget. Every completed form contributes one transcript value while definitions and assignments remain visible to later forms. The full run defines square, make-adder, add-five, and base; add-five keeps the local x value 5 even after the global base changes. The second run stops after five forms and reports four forms still pending. This form budget does not bound recursion or work inside a single form; the packaged browser runtime still supplies the lower-level execution limits.

    How can an evaluator turn failure from a terminal error into a request to resume an earlier choice point?

    Answer require evaluates its predicate in the same continuation system. A true predicate succeeds with ok; a false predicate invokes the next predicate alternative, which ultimately returns to the most recent amb choice. all-values repeatedly calls the next alternative supplied by each success. The first run exhausts the selected finite search and reports complete. The second deliberately stops after four solutions and reports truncated. The lesson evaluator covers ordered amb choices, require, procedure application, and success or failure continuation transfer.

    Which responsibilities belong to eval, which belong to apply, and which data structures connect the two?

    Answer apply-procedure receives already classified procedure values. A host procedure receives the argument list through apply. A compound-procedure record supplies parameters, body expressions, and its creation environment; application extends that environment with a fresh frame and evaluates the body sequence. The reused canonical driver exercises every core form in one persistent guest environment and makes the educational eval/apply loop visible.

    What information must the evaluator retain so a procedure can be applied later in the environment where it was created?

    Answer The second program tags a host + procedure as primitive data and applies it after unwrapping its implementation. list-of-values records left-to-right operand evaluation with explicit probes. The guest truth predicate returns false for #f, so zero and a quoted symbol both count as true. Together these records define the lesson evaluator protocol for frames, procedures, arguments, and truth tests.

    What becomes possible when the evaluator accepts the same list structures that ordinary procedures can construct and transform?

    Answer The second program treats a quadratic expression as data and recursively replaces the symbol x with 3. The transformation preserves arithmetic as symbolic data. arithmetic-eval then interprets + and * and produces 16. The lesson evaluator processes the listed arithmetic forms through the same explicit representation shared by the producer and transformer.

    How can two evaluators return the same value while performing different argument work?

    Answer In the second expression the body uses x twice. Strict evaluation computes probe once and binds the resulting 10. The non-memoized normal evaluator forces the saved expression for each lookup, so probe runs twice. Both return 20, but the work differs. These two evaluators are explicit guest models; they do not switch the evaluation order of the host Lispex program running them.

    How can an evaluator postpone argument work while preventing repeated references from recomputing the same expression?

    Answer force-it evaluates a fresh thunk once, rewrites the same mutable record to evaluated-thunk, stores the result, and discards the saved expression and environment. The unused argument therefore performs no probe. The duplicated x expression is demanded twice but probes once. The false branch of the selected if is never evaluated. This evaluator implements call by need for the listed forms under an explicit work budget.

    What happens to list abstraction when the evaluator delays every compound-procedure operand automatically?

    Answer integers-from and lazy-map use the same ordinary recursive syntax. Their recursive calls appear as delayed y operands to lazy-cons, so the infinite remainder is represented by memoized evaluator thunks. take is the demand boundary: primitive cons needs actual argument values, so it forces exactly the requested number of heads and tails into a finite host list. This example integrates lazy lists with the lesson evaluator and makes the consumer demand explicit.

    How can the program describe valid answers while the continuation protocol owns the order of alternatives and backtracking?

    Answer The Pythagorean program reuses the same continuations with numeric choices. Ordering constraints remove permutations and the square equality accepts two triples in the finite range 1 through 10. Both examples use depth-first finite lists and record every choice reached within that range.

    How does one query preserve possible variable bindings while a later query narrows those possibilities?

    Answer qeval recognizes and as a pipeline. The first query creates possible who bindings for all programmers. The second query receives each frame separately and keeps only the frame whose same who also supervises under bob. This lesson materializes the finite frame stream as a list while preserving the pipeline protocol.

    What additional machinery lets a query match not only stored facts but also conclusions derived from reusable rules?

    Answer simple-query combines direct assertion matches with rule results. A rule result unifies the query pattern with the renamed conclusion, then sends the resulting frame through qeval on the rule body. and pipelines frames, or appends alternatives, and not keeps a frame only when its subquery has no result under that frame. The explicit depth is the exact finite resource boundary for rule expansion.