The other night I was having a discussion with Wybiral and aks44 and lnostdal about continuations. I was trying to do my best to elucidate this somewhat difficult concept, but finally I was simply told to shut up and show the code So... in order not to end up looking all foolish, I of course had to hack something up to demonstrate.
So, this is in Scheme, and what we're trying to do is to have two binary search trees that we want to merge and flatten so that we get the elements of the trees in ascending order. Of course, this can be performed by using two inorder traversals of the trees. The question remains how to interleave the processing. So what we've got to begin with is:
Using Scheme's yield/force generator mechanism would of course work, but we'll just use two raw continuations here in a sort of coroutine arrangement. Think of threads, without threads.Code:(define tree_a '(20 (10 5 12) (30 25 37))) (define tree_b '(18 (9 8 16) (32 22 49))) (define (inorder tree) (if (not (null? tree)) (if (pair? tree) (begin (inorder (cadr tree)) (inorder (car tree)) (inorder (caddr tree))) (print tree)))) ; "print is a magic function that consumes item
I also want to keep our inorder traversal completely ignorant of what we're doing to its execution, so we need to hide some sort of a "traffic cop" into "print". Really cool (pure-functional) coroutines pass each others' continuations between each other as parameters (the call/cc evaluates to a value you pass the continuation as parameter when you call it, we don't do that here), juggling state like that, but I'm going to use side-effects here to just store a continuation in a variable from where it can be resumed at will, I think that illustrates a point better:
EDIT: This was just fixed, I removed the dependency on firing up the other tree from inside print, which was wrong. The explanation below refers to some old stuff that was since removed... I think this code is much more clear about what it does on its own.
The "store-and-call" bit is where we use call/cc to capture the current state of the computation into a variable that is passed as a parameter into the lambda, which in turn stores it in the variable, and does the same to the value we're trying to print (but are not allowed to print yet). Then we simply call into whatever is in the function parameter.Code:(define the-other-continuation 'nil) ; The other continuation (define bound 'nil) ; The value "the other continuation" waits to print (define (print val) (if (or (eq? bound 'nil) (> val bound)) (let ((old-cont the-other-continuation)) (call/cc (lambda (cont) (set! the-other-continuation cont) (set! bound val) (old-cont))))) (display val)) (define (run) (call/cc (lambda (cont) (set! the-other-continuation cont) (inorder tree_a))) (inorder tree_b) (display bound)) ; ugly hack.. :(
The "cond" is a gatekeeper that makes sure print suspends if the traversal calling print can't print yet -- there is a smaller value "queued up" by the waiting continuation. The first check simply makes sure that we don't go ahead without letting tree_b try first. It's the second one that does the actual comparison. Once we can be sure it's the other continuation that is waiting again, we are allowed to print!
Now, there's one issue with the code here and that is that when the first continuation to terminate does so, it just leaves the other one hanging -- the state is just discarded like any value as the program ends. So I need to actually explicitly print the last "bound"... my brain came to its limit for the time being when trying to avoid infinite loops with strategies to wake up the waiting continuation
Anyway, I'll try to think of other simple examples... would like to see others' too, as I'm trying to get "fluent" in continuation-passing-style
Bookmarks