I saw a piece of code from the website http://www.ccs.neu.edu/home/shivers/newstyle.html:
> (defun element-generator ()
(let ((state '(() . (list of elements to be generated)))) ;() sentinel.
(let ((ans (cadr state))) ;pick off the first element
(rplacd state (cddr state)) ;smash the cons
ans)))
ELEMENT-GENERATOR
> (element-generator)
LIST
> (element-generator)
OF
> (element-generator)
ELEMENTS
> (element-generator)
TO
> (element-generator)
BE
> (element-generator)
GENERATED
I don't understand how the function remembers the state. Isn't state
redefined to the whole list each time the function runs? And why the two layers of let
(which is necessary)? It'd be appreciated if someone is able to explain how this function works.
It's out of the scope of CL standards. Some CL implementations might do these strange things, but other might not. I remember I found something similar when I was learning CL. My version was this:
What's happening here, and in your code, is that the optional
x
andstate
are constants. It exist only one of them and if you change it you change it permanently. In my example; if the default value had been(cons -1 ())
it would always return 0 if I didn't supplyx
. Why you have two nested lets is because(cadr state)
is different before and after therplacd
. I urge you to remove it and replaceans
with(cadr state)
to see the difference.Here is how you example code would look if you were to want the same functionality within the standard:
Here you get a new closure for every instancing of element-generator and when you call the function it returns you will get elements from that closure. See how the two functions returned has their own version of the list. Since we're not hacking the standard we might as well rewrite it to mutate state instead of the list:
It will behave identical.
That is a static list, but rplacd changes it anyway... modifying the function definition so that next time its run the list is shorter. If you looked at the function definition between calls you would see it getting shorter. Some lisp implementations are better at showing a function definition than others.
You should only use destructive list operations when you just created the list (or you really understand what you are doing).
The value of
state
in(let ((state '(() . (list of elements to be generated)))) ...)
is a quoted literal, and it is being modified (which, as explained in this answer is undefined behavior). This behavior has been discussed other questions, such as:Above defines a function.
Here were define a local variable. There is literal data in the code. Think of the Lisp function itself data, and part of the code is this construct with the list. In the Common Lisp standard it is undefined what happens when one tries to modify this data object.
Since it is a literal data object, there is only one of it. The variable does not get a binding to freshly consed data. So on each call, here the variable points to the same literal data.
Above creates a temporary variable and saves the first element.
In above code the first element gets removed from the list. As mentioned already this is not recommended practice, since the list is literal data.
Above returns the saved value.
This
LET
idiom is in Common Lisp already provided by the macroPROG1
.