
[Mark Hammond]
... Thanks for the considerable time it must be taking to enlightening us!
You're welcome, but the holiday weekend is up and so is my time. Thank (all of) *you* for the considerable time it must take to endure all this <wink>! Let's hit the highlights (or lowlights, depending on your view):
... Im a little confused by how these [continuations] work in practice.
Very delicately <no wink>. Eariler I posted a continuation-based implementation of generators in Scheme, and Sam did the same for a hypothetical Python with a call/cc equivalent. Those are typical enough. Coroutines are actually easier to implement (using continuations), thanks to their symmetry. Again, though, you never want to muck with continuations directly! They're too wild. You get an expert to use them in the bowels of an implementation of something else.
... It is clear to me how you can capture the "state" of a running program. Indeed, this is exactly what it seems generators and coroutines do.
Except that generators need only worry about their own frame. Another way to view it is to think of the current computation being run by a (real <wink>) thread -- then capturing a continuation is very much like making a frozen clone of that thread, stuffing it away somewhere for later thawing.
With continuations, how is the state captured or created?
There are, of course, many ways to implement these things. Christian is building them on top of the explicit frame objects Python already creates, and that's a fine way for Python. Guido views it as cloning the call stack, and that's accurate too.
Anyway, in implementation terms a continuation "is like" what a coroutine would be if you could capture its resumption state at any point (even without the coroutine's knowledge!) and assign that state to a vrbl.
This makes sense, although it implies a "running state" is necessary for this to work.
In implementations (like Chris's) that do it all dynamically at runtime, you bet: you not only need a "running state", you can only capture a continuation at the exact point (the specific bytecode) you run the code to capture it. In fact, there *is* "a continuation" at *every* dynamic instance of every bytecode, and the question is then simply which of those you want to save <wink>.
In the case of transfering control to somewhere you have never been before (eg, a goto or a new function call) how does this work?
Good eye: it doesn't in this scheme. The "goto" business is a theoretical transformation, in a framework where *every* operation is modeled as a function application, and an entire program's life is modeled as a single function call. Some things are very easy to do in theory <wink>.
Your example:
def test(): i = 0 global continuation continuation = magic to resume at the start of the next line i = i + 1 return i
My main problem is that this looks closer to your description of a kind-of one-sided coroutine - ie, instead of only being capable of transfering control, you can assign the state. I can understand that fine.
Good!
But in the example, the function _is_ aware its state is being captured - indeed, it is explicitely capturing it.
In real life, "magic to resume at the start of the next line" may be spelled concretely as e.g. xyz(7) or even a.b That is, anywhere in "test" any sort of (explicit or implicit) call is made *may* be part of a saved continuation, because the callee can capture one -- with or without test's knowledge.
My only other slight conceptual problem was how you implement functions, as I dont understand how the concept of return values fits in at all.
Ya, I didn't mention that. In Scheme, the act of capturing a continuation returns a value. Like so: (define c #f) ; senseless, but Scheme requires definition before reference (define (test) (print (+ 1 (call/cc (lambda (k) (set! c k) 42)))) (newline)) The function called by call/cc there does two things: 1) Stores call/cc's continuation into the global "c". 2) Returns the int 42.
(test) 43
Is that clear? The call/cc expression returns 42. Then (+ 1 42) is 43; then (print 43) prints the string "43"; then (newline) displays a newline; then (test) returns to *its* caller, which is the Scheme shell's read/eval/print loop. Now that whole sequence of operations-- what happens to the 42 and beyond --*is* "call/cc's continuation", which we stored into the global c. A continuation is itself "a function", that returns its argument to the context where the continuation was captured. So now e.g.
(c 12) 13
c's argument (12) is used in place of the original call/cc expression; then (+ 1 12) is 13; then (print 13) prints the string "13"; then (newline) displays a newline; then (test) returns to *its* caller, which is *not* (c 12), but just as originally is still the Scheme shell's read/eval/print loop. That last point is subtle but vital, and maybe this may make it clearer:
(begin (c 12) (display "Tim lied!")) 13
The continuation of (c 12) includes printing "Tim lied!", but invoking a continuation *abandons* the current continuation in favor of the invoked one. Printing "Tim lied!" wasn't part of c's future, so that nasty slur about Tim never gets executed. But:
(define liar #f) (begin (call/cc (lambda (k) (set! liar k) (c 12))) (display "Tim lied!") (newline)) 13 (liar 666) Tim lied!
This is why I stick to trivial examples <wink>.
And one final question: In the context of your tutorial, what do Chris' latest patches arm us with? Given my new-found expertise in this matter <wink> I would guess that the guts is there to have at least co-routines, as capturing the state of a running Python program, and restarting it later is possible. Im still unclear about continuations WRT "without the co-routines knowledge", so really unsure what is needed here...
Christian is taking his work very seriously here, and as a result is flailing a bit trying to see whether it's possible to do "the 100% right thing". I think he's a lot closer than he thinks he is <0.7 wink>, but in any case he's at worst very close to having full-blown continuations working. Coroutines already work.
The truly final question:-) Assuming Chris' patches were 100% bug free and reliable (Im sure they are very close :-) what would the next steps be to take advantage of it in a "clean" way? ie, assuming Guido blesses them, what exactly could I do in Python?
Nothing. What would you like to do? Sam & I tossed out a number of intriguing possibilities, but all of those build *on* what Christian is doing. You won't get anything useful out of the box unless somebody does the work to implement it. I personally have wanted generators in Python since '91, because they're extremely useful in the useless things that I do <wink>. There's a thread-based generator interface (Generator.py) in the source distribution that I occasionally use, but that's so slow I usually recode in Icon (no, I'm not a Scheme fan -- I *admire* it, but I rarely use it). I expect rebuilding that on Christian's work will yield a factor of 10-100 speedup for me (beyond losing the thread/mutex overhead, as Chris just pointed out on c.l.py resumption should be much faster than a Python call, since the frame is already set up and raring to go). Would be nice if the language grew some syntax to make generators pleasant as well as fast, but the (lack of) speed is what's really killing it for me now. BTW, I've never tried to "sell" coroutines -- let alone continuations. Just generators. I expect Sam will do a masterful job of selling those. send-today-don't-delay-couldn't-give-or-receive-a-finer-gift-ly y'rs - tim