Continuations in Common Lisp with cl-cont
Continuations are getting some attention these days - there’s heated discussions about getting it into Java, there’s continuation-based web frameworks, and newly released Arc even includes such a library.
Perhaps I’m too old fashioned, but I don’t think continuation-based web frameworks are all that they’re cracked up to be, but I see that it’s a lot easier to do a lot of things with them than others. I see some issues with these that I’m not quite sure how to solve, and perhaps some other kinda of constructs could be better.
In any case, all this talk about continuations have made me more interested in them, and although Common Lisp does not support continuations properly, I searched around and found two packages this: arnesi and cl-cont. The latter is used to implement Weblocks. The two packages are very similar and can almost be used interchangably. cl-cont creator Slava Akhmechet explains the differences:
“By the time I understood everything I needed, I already had an almost complete WITH-CALL/CC implementation, so I didn’t want to throw it away. Also, CL-CONT does a compile time transformation while ARNESI/CC uses an interpreter for a subset of Common Lisp. The approaches are slightly different. However, you can use ARNESI/CC instead of CL-CONT with Weblocks with very minor modifications (they use KALL to restore continuations, while CL-CONT allows you to just use FUNCALL).”
I started looking at cl-cont, but the homepage isn’t very helpful. It contains some basic examples that are better implemented as normal functions, so I didn’t really get it, to say the least. Luckily, the posting in the Weblocks Google group contains a link to a blog post by Bill Clementson, explaining continuations using arnesi.
That post made things a lot more easier to understand… Thanks Bill!
My goal was simple: to be able to define a sequence of operations, and enable me to return after each operation and continue to the next one at will… Which is kind of what continuations allows you to do. Given the following code, the first call to foo should give me the value 1, and I should then be able to call something to make the control flow continue where it stopped to give me the value 2.
(defun foo ()
(with-yield foo
(yield 1)
(yield 2)))
The following macro, which I’ve tested with cl-cont, enables me to do that (perhaps it works with arnesi as well, I don’t know).
(defmacro with-yield (name &rest body)
`(with-call/cc
(flet ((yield (x)
(let/cc k
(setq ,name k)
x)))
,@body)))
Now, I can do as follows:
CL-USER> (defun foo ()
(with-yield foo
(yield 1)
(yield 2)))
FOO
CL-USER> (foo)
1
CL-USER> (funcall foo)
2
CL-USER> (funcall foo)
; No value
We could also make it generate a lot more numbers, if we wanted to.
CL-USER> (with-yield foo
(loop for i upto 100000000000
do (yield i)))
0
CL-USER> (funcall foo)
1
CL-USER> (funcall foo)
2
CL-USER> (funcall foo)
3
......
CL-USER> (funcall foo)
232
CL-USER> (funcall foo)
233
CL-USER> (funcall foo)
234
… and so on.
Fun, eh? Now I just have to figure out how to use this for something practical. Perhaps a web framework? Hmmmmm…