[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Using continuations in user-interfaces



At 3:07 PM +0000 12/22/01, Jamie Elliott wrote:
>Is it simply that the GUI library used in MrEd can't cope with continuations
>that are invoked across module boundries, or would this continuation thing
>fail to work in *any* UI system?  I would be bitterly disappointed if the
>latter were true - when I first realised that there was a connection between
>continuations and interactive programs, I experienced one of those great
>"Aha!" moments that all programmers live for.

No, rest assured that it is not intrinsic to a UI system; but there 
are more obstacles to overcome if you want to do this with DrScheme.

There are the two problems with your implementation, as it stands. 
First, the currently visible list of windows is not part of the 
control state, so it isn't grabbed by call/cc. In other words, 
changing the visibility of a window is an imperative action.  To fix 
this, I suggest that you make each of the steps in your wizard appear 
in the same frame and then use dynamic-wind when changing the 
visibility (restoring the old visibility in the post-thunk).

The second problem is a little more subtle, but the solution to the 
first problem also overcomes this problem -- read on if you want to 
understand that continuation invocation error message.

There are dynamic barriers to continuation invocation installed 
during evaluation. For example, if you grab a continuation on one 
thread and try to invoke it on another thread, the invocation will 
fail. The continuation grabbed the control state of the original 
thread and DrScheme decrees that it can only be restored on that one 
thread. In principle, this is a bug (or at least a missing feature) 
but (as I understand it) fixing this is quite complex, due to the 
implementation technique used for threads. Matthew Flatt can probably 
explain these details better than I could.

There is a similar barrier installed during nested event handling. To 
see what that means, I have to tell a little more about how GUI 
events are handled in DrScheme. For simplicity, what I say below 
assumes that there is one single eventspace. Once you understand the 
below, multiple eventspaces are not too hard to generalize to.

There is a single thread that handles all of the events that are 
triggered. This one thread blocks until an event is ready in the 
event queue. When an event appears, it finds the corresponding 
callback, invokes it and blocks again until a new event appears. If 
you were to grab a continuation on one event callback and invoke it 
on a subsequent event callback, this would work fine -- the 
continuation would be delimited at the original event handler and 
would restore that context on the event where it was invoked (this is 
what happens when you grab and invoke continuations across prompts in 
DrScheme's repl).

There is another mode for handling events, however. When a dialog is 
created or when `yield' is called, DrScheme creates a nested event 
loop. For example, consider this button's callback:

   (define button% (make-object button% "label" frame (lambda (x y) 
... (yield) ...))

When the button is clicked, first DrScheme will evaluate the code in 
the first ellipses. Then, when it gets to the `yield', it starts a 
nested event handler. This handles some number of events 
(unspecified, but see the docs on yield for how to control that) and 
returns. Then, the second set of ellipses is evaluated.

This is how dialogs work. Imagine you choose the file|open menu item 
of DrScheme. That invokes the menu item callback. It does some work 
and creates a dialog to ask the user which file to open. But, in 
order to ask which file, we have to handle more events. Then, when 
the user has chosen a file, the dialog closes and the rest of the 
original event (the file|open menu callback) is completed and the 
window is opened.

There is another barrier installed around the nested event callback. 
That is, if you grab a continuation and try to invoke it on a nested 
event callback, the invocation will fail. That's why your program was 
failing. Here's a shorter example to see what is going on.

;; grab a continuation
(let ([k (call/cc (lambda (x) x))])
;; queue an event to invoke the continuation
   (queue-callback
    (lambda ()
      (k #f)))
;; start a nested event handler (and fail when the queue'd event is handled)
   (yield (make-semaphore 0)))

Robby