June 09, 2005

Continuing ever onward

As I go typing up notes and such, I figured I'd write this up as well.

A couple of days ago I made vague reference to the large number of continuations $WORK_PROJECT creates when running reports, and its heavy use of them in general. Since it's a pretty good example of places that're worth using continuations, I figured I'd go into some detail.

Assume, for a moment, that you've got an interactive application that has a built-in menu system. The user chooses a menu option, a subroutine is called, and at some point control gets dropped back to the menu. (Your basic modal application. We shall set aside the modal / non-modal argument here, since that way lies vi vs emacs fights, and we just don't want to go there. Well, not right now at least) At any point in the execution of the code, even deep in nested subroutine calls, the program can bail back to the menu. Basically you want to dump whatever you're doing and just wait on input again.

Now, how would you normally do this? You might think that since you're calling into the menu selection handling sub, you could do a plain return from the sub call and get back to the menu. That doesn't work, though, since you may be two or three (or ten, or fifty) calls deep when the return to the menu happens, so something more complex is needed.

What's needed, essentially, is to backtrack all the way to the menu code whenever there's a bail-to-menu statement executed.

There are a few ways to do this. We'll assume for the moment that you've got full control over the compiler for the language in question. That's not necessary for some of these things, but it helps with others.

1) You could set things up so that there's a status code of "exit to menu", and the code for every call to a subroutine checks the status. This is, needless to say, really error-prone, not to mention tedious. Yech.

2) Since we've got control of the compiler, you could force it to emit that code for you. This isn't actually all that bad, as it just adds a little bit of boilerplate in one or two spots to note that the status is set and do an automatic return (propagating the status) if a called sub returns because of an exit to the menu.

3) You could throw an exception, if the system supports it. (I could do this, as it's parrot, but we're speaking generically) This can be an issue since you may have intermediate exception handlers that might catch and have to rethrow the exception. There's also the cost of unwinding the stack looking for exception handlers. That's not a huge problem, though, since you're then going to be waiting on user input, and if the unwinding takes long enough to notice you've really screwed up somewhere.

4) The compiler, since it knows about all the internal structures, can just mark where the stack is (amongst other things) and generate code to put things back in shape, skipping right back to the menu code.

5) Just take a continuation that'll get you back to the menu input code

Now, #4 is almost #5. Arguably it's the same thing, but it depends on the sort of low-level access you've got. And generally #5 is easier for app code, since something else is doing the work.

As a for example, the code I'm using looks like this to set up a continuation to return to the menu and save it in the global store for later fetching:

$P3486 = new Continuation
set_addr $P3486, MainLoop
store_global 'decision::prompt_continuation', $P3486
MainLoop:

and to jump back to the menu because code said bail:

$P3508 = global 'decision::prompt_continuation'
invoke $P3508

The $Pxxx things are just parrot temps. As you can see, it's... simple. Really simple. No muss, no fuss, works just fine. And the nice thing is that I can reuse the continuation over and over, since continuations can be reusable. Woohoo! Efficiency even, combined with (as you can see) dead-simple code. It's a good thing.

We could certainly take the continuation after the mainloop label, but it worked out better for me in this case. (There's some elided code between the continuation and the mainloop label -- $WORK_LANGUAGE declares that initialization code runs before the main loop and can bail to the main loop at any time)

Isn't simple nice? Even better is that you can do this and not know squat about how continuations work. They just do, and it can all be magic.

Posted by Dan at June 9, 2005 01:56 PM
Comments

This reminds me of an article by Matthew Fuchs -- Escaping the event loop: an alternative control structure for multi-threaded GUIs

Posted by: anonymous coward at June 13, 2005 04:41 AM

This could make resource management tricky. Does the 'finally' part of a try..finally still run if a continuation is called inside it?

Posted by: Daniel James at June 15, 2005 04:02 PM

Oh.. of course not if you're not unwinding the stack. I guess you've just got to make sure your code is continuation safe. Oh well.

Posted by: Daniel James at June 15, 2005 04:14 PM