I have a firm understanding of how lazy evaluation and streams work.
However I'm having some trouble simply following the book at this point. I don't really undrestand what it is trying to tell me about delay, and here is why.
At page 321 it reads
Cons-streamis a special form defined so that(cons-stream 〈a〉 〈b〉)is equivalent to
(cons 〈a〉 (delay 〈b〉))
and a related footnote
[…] If
cons-streamwere a procedure, then […] evaluating(cons-stream 〈a〉 〈b〉)would automatically cause〈b〉to be evaluated, which is precisely what we do not want to happen. For the same reason,delaymust be a special form […]
So I conclude that I can't implement cons-stream nor delay, at least not with the tools I have been given at this stage in the book, so I don't understand what the quote above is trying to tell me, beside "it's kinda this, but not quite".
Later, though, there's a "section" of the book titled
Implementing delay and force
Which says something similar to before:
Delaycan be a special form such that(delay 〈exp〉)is syntactic sugar for
(lambda () 〈exp〉)
But again, how do I define delay if I don't know how to define a special form?
Paradoxically, immediately after one finds that
This implementation suffices for
delayandforceto work as advertised, but […]
(and the "but" has nothing to do with special forms but just with performance), but how can it work if it's not a special form, defined like this?
And even if I wanted to assume that delay is already defined in whatever Scheme interpreter I'm using (which is the case for this online compiler), I would still be unable to define a special form that makes use of it, such as cons-stream.
Bottom line, I don't know how to write anything for making the exercises.
Eventually, I found this answer showing how to define a special form, but the point is that the define-syntax used therein is not even mentioned in the whole book.
So how was I supposed to do the exercises?
You were supposed to just write the code explicitly according to the syntactical definitions, either as
or
or even
instead of
everywhere. It's not that bad. I did it once or twice, too.
memocan even be defined as a function pretty straightforwardly.In fact, this is how the text can plausibly be interpreted anyway. It is saying to you, if
delayandstream-conswere defined, then writingthiswould be exactly the same as writingthat.... well then, just go head and writethatin the first place!The longer way is to define your own interpreter, and have it handle those additional special forms - not "macros" - for you as needed.
You would write your programs as quoted lists, and use them as an input to the interpreter.
You can also fake it by doing whole program transformations, writing new source files out, and loading these transformed source files back with the same interpreter you're already using.
An intermediate way is to define your streams as stateful objects with explicit pull requests etc., have the constructor store the lambda function(s) inside, and build the rest on top of that.
Trying to define e.g. Hamming sequence with this kind of streams is actually an interesting exercise, forces you to deal with various issues explicitly that might otherwise remain implicit.