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
(begin(define(tagged-list?expressiontag)(and(pair?expression)(eq?(carexpression)tag)))(define(self-evaluating?expression)(or(number?expression)(string?expression)(boolean?expression)))(define(quoted?expression)(tagged-list?expression'quote))(define(if?expression)(tagged-list?expression'if))(define(lambda?expression)(tagged-list?expression'lambda))(define(begin?expression)(tagged-list?expression'begin))(define(definition?expression)(tagged-list?expression'define))(define(text-of-quotationexpression)(cadrexpression))(define(if-predicateexpression)(cadrexpression))(define(if-consequentexpression)(caddrexpression))(define(if-alternativeexpression)(cadddrexpression))(define(lambda-parametersexpression)(cadrexpression))(define(lambda-bodyexpression)(cddrexpression))(define(begin-actionsexpression)(cdrexpression))(define(definition-variableexpression)(if(symbol?(cadrexpression))(cadrexpression)(car(cadrexpression))))(define(definition-valueexpression)(if(symbol?(cadrexpression))(caddrexpression)(cons'lambda(cons(cdr(cadrexpression))(cddrexpression)))))(define(operatorexpression)(carexpression))(define(operandsexpression)(cdrexpression))(define(pair-bindingsvariablesvalues)(cond((and(null?variables)(null?values))'())((null?variables)(error"too many guest arguments"))((null?values)(error"too few guest arguments"))(else(cons(cons(carvariables)(carvalues))(pair-bindings(cdrvariables)(cdrvalues))))))(define(make-framevariablesvalues)(cons'*frame*(pair-bindingsvariablesvalues)))(define(first-frameenvironment)(carenvironment))(define(frame-bindingsframe)(cdrframe))(define(extend-environmentvariablesvaluesbase)(cons(make-framevariablesvalues)base))(define(lookup-variable-valuevariableenvironment)(if(null?environment)(error"unbound lazy guest variable"variable)(let((record(assocvariable(frame-bindings(first-frameenvironment)))))(ifrecord(cdrrecord)(lookup-variable-valuevariable(cdrenvironment))))))(define(define-variable!variablevalueenvironment)(let*((frame(first-frameenvironment))(record(assocvariable(frame-bindingsframe))))(ifrecord(set-cdr!recordvalue)(set-cdr!frame(cons(consvariablevalue)(frame-bindingsframe)))))(list'definedvariable))(define(make-procedureparametersbodyenvironment)(list'compoundparametersbodyenvironment))(define(compound-procedure?procedure)(tagged-list?procedure'compound))(define(procedure-parametersprocedure)(cadrprocedure))(define(procedure-bodyprocedure)(caddrprocedure))(define(procedure-environmentprocedure)(cadddrprocedure))(define(delay-itexpressionenvironment)(list'thunkexpressionenvironment))(define(thunk?object)(tagged-list?object'thunk))(define(evaluated-thunk?object)(tagged-list?object'evaluated-thunk))(define(force-itobject)(cond((thunk?object)(let((result(actual-value(cadrobject)(caddrobject))))(set-car!object'evaluated-thunk)(set-car!(cdrobject)result)(set-cdr!(cdrobject)'())result))((evaluated-thunk?object)(cadrobject))(elseobject)))(define(actual-valueexpressionenvironment)(force-it(lazy-evalexpressionenvironment)))(define(true?value)(not(eq?value#f)))(define(eval-ifexpressionenvironment)(if(true?(actual-value(if-predicateexpression)environment))(lazy-eval(if-consequentexpression)environment)(lazy-eval(if-alternativeexpression)environment)))(define(eval-sequenceexpressionsenvironment)(cond((null?expressions)'ok)((null?(cdrexpressions))(lazy-eval(carexpressions)environment))(else(actual-value(carexpressions)environment)(eval-sequence(cdrexpressions)environment))))(define(eval-definitionexpressionenvironment)(define-variable!(definition-variableexpression)(actual-value(definition-valueexpression)environment)environment))(define(list-of-arg-valuesexpressionsenvironment)(if(null?expressions)'()(cons(actual-value(carexpressions)environment)(list-of-arg-values(cdrexpressions)environment))))(define(list-of-delayed-argsexpressionsenvironment)(if(null?expressions)'()(cons(delay-it(carexpressions)environment)(list-of-delayed-args(cdrexpressions)environment))))(define(apply-lazyprocedureargument-expressionscalling-environment)(cond((procedure?procedure)(applyprocedure(list-of-arg-valuesargument-expressionscalling-environment)))((compound-procedure?procedure)(eval-sequence(procedure-bodyprocedure)(extend-environment(procedure-parametersprocedure)(list-of-delayed-argsargument-expressionscalling-environment)(procedure-environmentprocedure))))(else(error"not a lazy guest procedure"procedure))))(define(lazy-evalexpressionenvironment)(cond((self-evaluating?expression)expression)((symbol?expression)(lookup-variable-valueexpressionenvironment))((quoted?expression)(text-of-quotationexpression))((if?expression)(eval-ifexpressionenvironment))((lambda?expression)(make-procedure(lambda-parametersexpression)(lambda-bodyexpression)environment))((begin?expression)(eval-sequence(begin-actionsexpression)environment))((definition?expression)(eval-definitionexpressionenvironment))((pair?expression)(apply-lazy(actual-value(operatorexpression)environment)(operandsexpression)environment))(else(error"unknown lazy guest expression"expression))))(defineprobes0)(define(probevalue)(set!probes(+probes1))value)(defineprimitive-bindings(list(cons'++)(cons'--)(cons'**)(cons'//)(cons'==)(cons'<<)(cons'>>)(cons'listlist)(cons'conscons)(cons'carcar)(cons'cdrcdr)(cons'null?null?)(cons'pair?pair?)(cons'probeprobe)))(defineglobal-environment(list(cons'*frame*primitive-bindings)))(defineguest-program'(begin(define(lazy-consxy)(lambda(selector)(selectorxy)))(define(lazy-carpair)(pair(lambda(xy)x)))(define(lazy-cdrpair)(pair(lambda(xy)y)))(defineones(lazy-cons1ones))(define(takestreamcount)(if(=count0)(quote())(cons(lazy-carstream)(take(lazy-cdrstream)(-count1)))))(takeones5)))(actual-valueguest-programglobal-environment))
Examples
Result—
Output
—
Value
—
Diagnostic
—
Execution trace0 / 0 events
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.