tree->generator in Common Lisp with cl-cont
2008-09-01
I’ve decided to try Slava Akhmechet’s cl-cont, a continuations library for Common Lisp. My little example for this purpose was to port the tree->generator function from Teach Yourself Scheme in Fixnum Days to Common Lisp. This function takes a tree and returns a function that will successively yield the leaves of the tree.
Here’s the code:
(defun/cc tree->generator (tree)
(let (generate-leaves)
(setf generate-leaves
(lambda (caller)
(labels ((recur (tree)
(cond ((null tree)) ; empty leaf: continue
((consp tree) ; recurse into branches
(recur (car tree))
(recur (cdr tree)))
('otherwise ; leaf with a value:
(call/cc
(lambda (rest-of-tree)
;; update generator
(setf generate-leaves
(lambda (new-caller)
(setf caller new-caller)
(funcall rest-of-tree)))
;; return the value
(funcall caller tree)))))))
(recur tree))
(funcall caller nil))) ; return value after exhaustion
(lambda ()
(call/cc generate-leaves))))
The comments are my own, so I could understand how this function works. I did also add the explicit caller argument to generate-leaves, keeping variables as local as possible is generally a good thing (and I didn’t like those dummy values). These changes aren’t specific to CL, I did a Scheme version with those too.
There was one problem that took me quite some time to solve: (SETF SYMBOL-FUNCTION) will always operate on the global binding, so I couldn’t use LABELS to define generate-leaves. Therefore, I had to use this trick of splitting the definition into a LET and a SETF to get a local function definition that could modify its own binding. In contrast, porting the continuations stuff was easy: that worked just like in Scheme.
Here’s a little usage example:
CL-USER> (defparameter *g* (tree->generator '((1 2) (3 (4 5))))) *G* CL-USER> (funcall *g*) 1 CL-USER> (funcall *g*) 2 CL-USER> (funcall *g*) 3 CL-USER> (funcall *g*) 4 CL-USER> (funcall *g*) 5 CL-USER> (funcall *g*) NIL