sicp.io
4.2.4 · Streams as lazy lists

A lazy evaluator can make an ordinary procedure behave like non-strict cons.

Define pairs, selectors, infinite lists, map, and take inside the guest language so delayed compound-procedure arguments supply the stream behavior.

Guiding question

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

  • Define lazy-cons as an ordinary compound procedure in the guest language
  • Select a head without forcing the delayed tail parameter
  • Create a self-referential infinite ones binding
  • Generate an unbounded integer list recursively
  • Map a guest procedure over a lazy list without constructing its full tail
  • Use a strict finite consumer to demand an exact finite result list

lazy-cons is written as a procedure that returns a selector procedure. Under the lazy evaluator, its x and y parameters are thunks. lazy-car applies the pair to a selector that returns x, so y remains unforced. The global definition of ones can therefore refer to ones in its own delayed tail: the reference is not demanded until lazy-cdr reaches it after the binding exists.

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.

SICP code7,774 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

    The self-referential list returns (1 1 1 1 1). The mapped integer list returns (1 4 9 16 25 36).

    Trace focus

    Find each lazy-cons application and inspect the delayed y operand. In the ones run, confirm that the self-reference is first forced only through lazy-cdr after define has installed the binding. In the mapped run, count how many integers-from and square applications are demanded by take rather than imagining a completed infinite list.

    Try it yourself

    Change the program and compare the result.

    Define lazy-filter and take the first five even integers. Then request the same prefix twice and inspect which evaluator thunks are reused after memoization.

    Show hint

    When a value fails the predicate, continue with the delayed tail. When it passes, construct one lazy-cons whose recursive filter call remains the delayed second argument.

    Complete this lesson

    0 of 23 lessons complete in this chapter0%