
I think I've pointed out that the main difference between GIO and HTk is that HTk uses composable events, while GIO, like most other Haskell GUI's (including GTk+hs) use callbacks. Now of course I'm biased, and I prefer events to callbacks, but I appreciate the arguments that have been made that including composable events in a GUI would simply involve adding too much heavy machinery which doesn't have anything to do with graphics. But I think I have a solution to this, which I would like to propose. This is that a GUI standard specify events, but *doesn't* specify that they be composable. Then they would be trivial to implement, and still (in my opinion) be better than callbacks. Also systems like HTk could of course add an extra module which did provide the composition operators. So, in detail, the GUI standard should have a new type Event a which means "Event carrying a value a". To generate for example something like a button click event we could have a function clicked :: Clickable widget => widget -> IO (Event ()) Thus, after this function is called, the returned "event" would happen each time the user clicks the button. A mouse click event might, for example, have type (Event (Int,Int)) giving the co-ordinates of the mouse pointer at the time of the click. To "listen" for events, two functions would be provided: sync :: Event a -> IO a which blocks until the event happens (or returns immediately if a queued occurence of this event has already happened), and setCallBack :: Event a -> (a -> IO ()) -> IO () which added an action to be performed each time the event happens. There are questions to be asked about what happens when you have multiple consumers of the same event. I think it should be guaranteed that when an event happens, only one consumer gets to hear about it, but which does not perhaps need to be specified. (I don't think this is very likely anyway.) Summary ------- Advantages over Composable Events. easier to implement. Advantages over callbacks: can easily be extended to composable events the "sync" operator is more powerful than callbacks (without being particularly hard to implement). For example, I think all or at least most of the call-back-based solutions to the "simple GUI Example" at John Meacham's page http://repetae.net/john/computer/haskell/gui/ contain the following bug: if the user clicks twice very rapidly on the button, the second click will be ignored. In general the click-handling routines can run concurrently, perhaps modifying common shared data, and leading to all sorts of trouble, unless the user uses locks. It seems clear that what is wanted here is sync, not callbacks.

On Thu, 06 Mar 2003 19:13:08 +0100
George Russell
Now of course I'm biased, and I prefer events to callbacks, but I appreciate the arguments that have been made that including composable events in a GUI would simply involve adding too much heavy machinery which doesn't have anything to do with graphics.
Why not just provide events AND callbacks AND composable events? The user of the library will choose what he deserves :-) Vincenzo

George Russell wrote:
I think I've pointed out that the main difference between GIO and HTk is that HTk uses composable events, while GIO, like most other Haskell GUI's (including GTk+hs) use callbacks.
Now of course I'm biased, and I prefer events to callbacks,
I'm biased too, but in the other direction. Apart from personal preference, the callback model is more common in mainstream GUI environments.
but I appreciate the arguments that have been made that including composable events in a GUI would simply involve adding too much heavy machinery which doesn't have anything to do with graphics.
But I think I have a solution to this, which I would like to propose. This is that a GUI standard specify events, but *doesn't* specify that they be composable. Then they would be trivial to implement, and still (in my opinion) be better than callbacks. Also systems like HTk could of course add an extra module which did provide the composition operators.
Does this mechanism allow the toolkit to determine whether an event is being listened for? Or would it force the toolkit to always generate all events just in case it's being listened for? The latter is unacceptable on X.
To "listen" for events, two functions would be provided:
sync :: Event a -> IO a
which blocks until the event happens (or returns immediately if a queued occurence of this event has already happened), and
This implies modality, which is usually a bad thing. Also, it sounds like a recipe for non-portability; what happens in response to all the other events which turn up while you're waiting for this specific event?
There are questions to be asked about what happens when you have multiple consumers of the same event. I think it should be guaranteed that when an event happens, only one consumer gets to hear about it, but which does not perhaps need to be specified. (I don't think this is very likely anyway.)
Personally, I'd prefer to allow multiple callbacks. Xt supports this
directly (each action has a list of callbacks); for toolkits which
only support a single callback, the abstraction layer would, when the
callback list wasn't empty, install a callback which iterates through
the callback list.
I suspect that multiple callbacks could be quite common; if so, it
would be better for the GUI system to implement this directly than for
each application to roll its own "callback multiplexer".
--
Glynn Clements

On Fri, 7 Mar 2003 01:12:22 +0000
Glynn Clements
Does this mechanism allow the toolkit to determine whether an event is being listened for? Or would it force the toolkit to always generate all events just in case it's being listened for? The latter is unacceptable on X.
We can have "automatically activating" streams; it would be useful to have the "mouse positions" streams, but it's unacceptable to always get mouse position, for example. So the idea is simple: just provide a "Var" (event and ability to write to it) constructor wich looks like mkAutomaticVar :: IO () -> IO () -> a -> Var a where the first two arguments are the actions to execute when the first listener is added and when the last listener is garbage collected. The matter is how to catch the garbage collection, maybe with weak pointers. I guess you know more than me, so tell me what you think. Again, since I am not biased, I would like to provide both events and callbacks. With multithreading, callbacks are easily derived from events.
To "listen" for events, two functions would be provided:
sync :: Event a -> IO a
which blocks until the event happens (or returns immediately if a queued occurence of this event has already happened), and
This implies modality, which is usually a bad thing. Also, it sounds like a recipe for non-portability; what happens in response to all the other events which turn up while you're waiting for this specific event?
What is modality? However, I think that when you are waiting for an event, you are implicitly discarding others. If you want to listen to two streams, either you merge them, or you use a separate thread for each stream.
I suspect that multiple callbacks could be quite common; if so, it would be better for the GUI system to implement this directly than for each application to roll its own "callback multiplexer".
I agree here. Vincenzo

Nick Name wrote:
Does this mechanism allow the toolkit to determine whether an event is being listened for? Or would it force the toolkit to always generate all events just in case it's being listened for? The latter is unacceptable on X.
We can have "automatically activating" streams; it would be useful to have the "mouse positions" streams, but it's unacceptable to always get mouse position, for example. So the idea is simple: just provide a "Var" (event and ability to write to it) constructor wich looks like
mkAutomaticVar :: IO () -> IO () -> a -> Var a
where the first two arguments are the actions to execute when the first listener is added and when the last listener is garbage collected. The matter is how to catch the garbage collection, maybe with weak pointers. I guess you know more than me, so tell me what you think.
I don't think that I understood any of that, so I'll just elaborate upon my main concern. Under X, you have to tell the X server which events it should actually send to the client (as opposed to Windows, which reports every event, and the code just ignores the ones it isn't interested in). Generally, you don't ask the X server to send you any events you don't actually need, as the bandwidth between the client and the X server may be limited. In particular, you don't ask it to send MotionNotify (mouse movement) events unless you actually want them. Consequently, you don't want the abstraction layer to be registering a mouse motion callback which actually ends up doing nothing.
Again, since I am not biased, I would like to provide both events and callbacks. With multithreading, callbacks are easily derived from events.
But what about the reverse? I.e. if the toolkit only provides callbacks, how do you get events? Presumably by registering callbacks which simply add an event to a queue. But does the event mechanism allow you to only register those callbacks which are actually required at any given time? It's important that a callback is de-registered as soon at is no longer required. And what about inter-related callbacks? E.g. for composite widgets (e.g. a scrolled window), both the composite widget and all of its individual components may provide callbacks. Sometimes, a single event may invoke multiple callbacks. Basically, it seems that this is moving far enough away from the toolkit's native behaviour that it may be making life harder for the application programmer.
To "listen" for events, two functions would be provided:
sync :: Event a -> IO a
which blocks until the event happens (or returns immediately if a queued occurence of this event has already happened), and
This implies modality, which is usually a bad thing. Also, it sounds like a recipe for non-portability; what happens in response to all the other events which turn up while you're waiting for this specific event?
What is modality?
Where the application switches between distinct modes, behaving differently in each mode.
However, I think that when you are waiting for an event, you are implicitly discarding others.
Do you mean "discarding" (i.e. nothing will ever happen in response to those events) or "ignoring" (i.e. the code performing the listen doesn't respond to those events, but something else might)? The former doesn't make sense; it's seldom the case that there is only one type of event which is meaningful at a given time.
If you want to listen to two streams, either you merge them, or you use a separate thread for each stream.
That doesn't sound like the way most toolkits work.
Either I've completely misunderstood what you're proposing, or it's so
far removed from typical GUI idioms that it's bound to be a mistake. I
hope that it's the former.
If you have a specific architecture in mind, one which could be
implemented in Haskell on top of backends which only provide
callbacks, then none of this is an issue. OTOH, if you're relying upon
having the backend provide an event stream, then that's a problem.
--
Glynn Clements

On Fri, 7 Mar 2003 11:07:39 +0000
Glynn Clements
I don't think that I understood any of that, so I'll just elaborate upon my main concern.
Under X, you have to tell the X server which events it should actually send to the client (as opposed to Windows, which reports every event, and the code just ignores the ones it isn't interested in).
Generally, you don't ask the X server to send you any events you don't actually need, as the bandwidth between the client and the X server may be limited. In particular, you don't ask it to send MotionNotify(mouse movement) events unless you actually want them.
Yes, this is a problem in any toolkit. What I said is that you can ask the GUI to send events only when there is someone listening to the event. I am pretty sure I can do this easily, but let me some day to produce an example. The idea is: -- You get a stream when you need it, with an IO action data Stream a = Stream (IO [a]) -- A function to create a stream newStream :: IO () -> IO () -> Stream a When you create a stream you pass two IO actions: one to "activate" the stream, on the first invocation of its IO action, and the other to "deactivate" (say, unregister the callbacks), when the last list produced with its IO action gets garbage collected. Don't say this is too complicate now, it might be simpler than it seems, and streams are important if we aren't going to turn haskell code into purely imperative programming. (this will follow in the other opened thread, of course :)). Another way is this: data Stream a = Stream (IO [a],IO ()) where the second component is an action that the user of the library explicitly calls when he does not need other inputs from the stream (which is then closed). This avoids the callback remaining installed until garbage collection, but I dislike this approach, is too few automatized. I have to think more, because I believe there is a third alternative to force an early, predictable uninstalling of the callback as soon as there are no more listeners. Vincenzo

On Fri, Mar 07, 2003 at 04:48:29PM +0100, Nick Name wrote:
Don't say this is too complicate now, it might be simpler than it seems, and streams are important if we aren't going to turn haskell code into purely imperative programming. (this will follow in the other opened thread, of course :)). I'd like to remind everyone that we are really aiming for mid-level complexity. It should be possible to implement high-level approaches like Fudgets or Fran on top of CGA, as such, making streams and other concepts mandatory is probably wrong.
I do not oppose having this syncronizing abstraction, but it would be great if it can be a little extension independant of the widget implementation. Isn't it possible to create a function syncOn such that it takes a callback, a user function and then waits till the callback is triggered? Isn't this just a matter of a) adding the user function and an "exit main loop" call to the callback handler and b) running the main loop and removing the callback? Hope I understand this issue, Axel.

On Fri, 7 Mar 2003 16:01:40 +0000
Axel Simon
I'd like to remind everyone that we are really aiming for mid-level complexity. It should be possible to implement high-level approaches like Fudgets or Fran on top of CGA, as such, making streams and other concepts mandatory is probably wrong.
Yes, but there's no issue in using a simple structure similar to my Var or M.Chackravarty's Port as the local state handler for widgets, and getting streams for free, without renouncing to callbacks and reading/writing the state, and all the usual imperative features. Besides, I don't see the point in considering streams such an "high level" abstraction: we have GetContents in the prelude, for example! Streams with explicit handling of the contained values are not FRAN, they are an usual abstraction in lazy functional programming, at least it's what I have always thought. Vincenzo

On Fri, 7 Mar 2003 11:07:39 +0000
Glynn Clements
And what about inter-related callbacks? E.g. for composite widgets (e.g. a scrolled window), both the composite widget and all of its individual components may provide callbacks.
Well, what I have in mind is that callbacks and events really are dual: it's the same thing to listen to an event or to install a callback. So a composite widget will have, as event, a (possibly tagged) merge of the composing widgets events.
Sometimes, a single event may invoke multiple callbacks.
If one wants to handle multiple events simultaneously, and to handle them with streams and not with callbacks, he has two choiches: merge the handler code, or (really easier and better) use multi-threading, as I do in my tests these days. There are examples wich just look nice with this approach, and look weird with callbacks, I'll post here but I don't know if I have time this weekend.
Basically, it seems that this is moving far enough away from the toolkit's native behaviour that it may be making life harder for the application programmer.
Well, I don't want to renounce to streams because having to handle everything with an IO action moves away from the functional programming! There are cases in wich a callback is just better, though. And I doubt that anyone is willing to abandon callbacks :)
However, I think that when you are waiting for an event, you are implicitly discarding others.
Do you mean "discarding" (i.e. nothing will ever happen in response to those events) or "ignoring" (i.e. the code performing the listen doesn't respond to those events, but something else might)?
If there is code listening to an event, be it a callback or a thread mapping onto a stream, it will receive the event. This is achieved with multithreading in my tests. But the thread wich performs a "waitevent" operation is waiting for just that event.
If you want to listen to two streams, either you merge them, or you use a separate thread for each stream.
That doesn't sound like the way most toolkits work.
Either I've completely misunderstood what you're proposing, or it's so far removed from typical GUI idioms that it's bound to be a mistake. I hope that it's the former.
Are we all talking of the same thing? I hope that I have understood well what George Russel is calling "Event": a datatype holding a value changing over time, wich allows waiting for a value (and possibly listening to the stream of all values); M. Chackravarty's Ports library is an example of this abstraction; It's just like using getContents instead of installing a callback for the keyboard: there are many cases in wich it's just better! Of course using streams for events is far away from traditional GUI paradigm, and of course monads exist because streams are uncomfortable in many situations, but streams are a typical abstraction in a lazy language and we cannot decide to drop them out so easily, if we can keep them. OTOH, we also need callbacks, as I previously stated.
If you have a specific architecture in mind, one which could be implemented in Haskell on top of backends which only provide callbacks, then none of this is an issue. OTOH, if you're relying upon having the backend provide an event stream, then that's a problem.
I have already posted working code in the thread "some random thought of my own", but I still have not handled the case of uninstalling callbacks (this issue is mentioned in the file "Var.hs"). However results are comfortable: my code for John Meacham example is not suffering of the "click too fast" problem, for obvious reasons; and it's highly readable. I would like you to read my code, if you have time. I am going to keep on working to transform the Var library in a non-toy thing: actually I started experimenting on Var's in summer, but had no time. This discussion is motivating me. An important note is that my code doesn't mean MY IDEAS, or someone will feel offended. :-) Vincenzo

Hello, There has been some discussion between the event and callback methods of connecting things. Can someone please explain, in practical terms, what the advantages there are to the event method? It sounds like an interesting idea, but I don't think I understand it at a practical level. The callback method is extremely intuitive for me: set button [ onClick ~: (\a -> handlerFunction:a) ] or addClickHandler button handlerFunction and when the button is clicked, it would run through the list of handlerFunctions (perhaps concurrently). Thanks, David J. Sankel

On Thu, 6 Mar 2003 19:33:51 -0800 (PST)
David Sankel
Can someone please explain, in practical terms, what the advantages there are to the event method?
I just forgot to reply to you; in my personal view, object oriented programming, and also event-based programming, introduce the need for too many shared/global variables to represent the stage you have reached in a computation. Somebody calls this "decoupling"; I have always wondered "decoupling of what from what", but this is another matter :) For example, consider John Meacham's simple example. As G.Russel pointed out, (almost) all the event-based implementation suffer from a problem: if the user clicks the button twice, fastly, the program does not exit. This is because the programs are written like onClick button callback1 callback1 = doSomething >> unregister callback1 >> onClick button callback2 = doSomethingElse The first ">>" in callback1 can be the point on wich the user clicks the button for the second time! The correct code, with callbacks, should have been: onClick button callback callback = do lock globalState s <- readGlobalState writeGlobalState (s+1) unlock globalState case s of 1 -> doSomething 2 -> doSomethingElse As you can imagine, if we have a time-dependent computation, the number of branches in the "case" statement of the callbacks grows big. In this cases, an alternative solution is to get the stream of the clicks and then zip it with the list of action you want to perform. In this case, you are just respecting the specification that "two consecutive clicks will make the program exit", because you can tell that you are catching all the clicks. Of course there might be solutions really more clever than mine in the callback approach. It's just one of the difficulties I found in event driven programming, and one of the reasons I like functional programming instead of object oriented one (if you think about it, you'll find that things behave quite similar). Vincenzo

For example, consider John Meacham's simple example. As G.Russel pointed out, (almost) all the event-based implementation suffer from a problem: if the user clicks the button twice, fastly, the program does not exit.
This is because the programs are written like
onClick button callback1
callback1 = doSomething >> unregister callback1 >> onClick button callback2 = doSomethingElse
The first ">>" in callback1 can be the point on wich the user clicks the button for the second time!
Just for the record: this is *not* true for most (all?) event based implementations as they are *not* concurrent (at least not pre-emptively concurrent). Somewhere down, there is an event loop -- a loop that waits for an event, executes a callback, and loops again. Even though more events can happen while executing the callback, it will only propegate into Haskell once the callback returns and the event loop pops it off the OS event queue. [note: You can achieve more concurrency in Haskell by using Haskell threads but even than one has to wait till the callbacks are done or otherwise the runtime system sits idle while waiting for the next event (as this is a C call).] -- Daan

On Tue, 11 Mar 2003 09:27:29 +0100
Daan Leijen
Just for the record: this is *not* true for most (all?) event based implementations as they are *not* concurrent (at least not pre-emptively concurrent).
Ok, and that's why I can't be left alone doing something serious ;) Sorry about this! V.

On Tue, 11 Mar 2003 09:27:29 +0100 Daan Leijen
wrote: Just for the record: this is *not* true for most (all?) event based implementations as they are *not* concurrent (at least not pre-emptively concurrent).
Ok, and that's why I can't be left alone doing something serious ;) Sorry about this! Right now Daan comment is surely right. But a programmer has to be aware of it. You could still fork a process that eventually disconnects the callback and you can expect that the Haskell runtime system does continue to run concurrently with the GUI, either due to OS threads or to some hack ("idleAdd priorityNormal (yield >> return True)" in Gtk). But yes, it is
On Tue, Mar 11, 2003 at 11:45:05AM +0100, Nick Name wrote: probably enough to make the user aware of these things. Axel.

On Tue, 11 Mar 2003 11:45:05 +0100, Nick Name
On Tue, 11 Mar 2003 09:27:29 +0100 Daan Leijen
wrote: Just for the record: this is *not* true for most (all?) event based implementations as they are *not* concurrent (at least not pre-emptively concurrent).
Ok, and that's why I can't be left alone doing something serious ;) Sorry about this!
It wasn't personal or anything. Just wanted to make sure that this "rumor" wouldn't get a life of its own :-) --- Daan.
V. _______________________________________________ GUI mailing list GUI@haskell.org http://www.haskell.org/mailman/listinfo/gui

On Tue, 11 Mar 2003 15:35:14 +0100
Daan Leijen
It wasn't personal or anything. Just wanted to make sure that this "rumor" wouldn't get a life of its own :-)
Of course, thread support in GHC (and other preemptive implementations of concurrent haskell) should be heavily considered in the specification stage. I don't have any skill to speak about that, but I fear that a GUI not designed for concurrency from the start will cause problems to concurrency till its end; please take some decision on this topic before starting the specification. Vincenzo

Am I overlooking some subtle semantic differences? To me, it looks like (non-composable) events can easily be implemented on top of callbacks using Control.Concurrent.Chan. And it shouldn't be too difficult to implement callbacks on top of events... If the above is true, then I'd say we implement callbacks first, we can easily add events later. Events look like a higher-level concept to me, as they seem to require concurrency to be meaningful. They could be added as an add-on-library, but IMHO we probably don't need them in the spec right now. Which leads us to another big question: Concurrency. The CGA should probably be safe to use from many threads at once (although many backends aren't, so the library would have to do a lot of synchronizing). Should callbacks for multiple clicks on one button really run concurrently, or should it behave like most toolkits for other languages? I know of no "conventional" toolkits where event handlers for one event source are run concurrently. It sounds dangerous, but it might have advantages. Cheers, Wolfgang

Wolfgang Thaller wrote:
Am I overlooking some subtle semantic differences? To me, it looks like (non-composable) events can easily be implemented on top of callbacks using Control.Concurrent.Chan.
I hope so (see my recent worried-sounding reply to Vincenzo); I suspect the key point is whether the application programmer can control (directly or indirectly) whether or not a callback is registered. Basically: registering a callback which does nothing and not registering a callback aren't necessarily the same thing.
And it shouldn't be too difficult to implement callbacks on top of events...
But composing the two conversions needs to be the identity function, or at least a very close approximation to it. The kind of events that you would get from XtAppNextEvent() and from registering callbacks that manufacture events are very different. The raw X events from XtAppNextEvent() are mostly meaningless to an Xt program. The complexity of XtDispatchEvent() (plus all of the widget-class-specific handlers which it may invoke) is such that you can't realistically comprehend the connection between the raw event stream and the behaviour at the callback level. Most Xt programs just call XtAppMainLoop(), which is basically just: do { XtAppNextEvent(app, &event); XtDispatchEvent(&event); } while(app->exit_flag == FALSE); The only plausible reason why you might wish to implement your own main loop is if you need to do something else as well (without actually interfering with the event stream in any way). Ultimately, you still have to pass all events to XtDispatchEvent() in the order in which they arrived, unless you actually want to spend all of your time trying to figure out why the program no longer works.
If the above is true, then I'd say we implement callbacks first, we can easily add events later. Events look like a higher-level concept to me, as they seem to require concurrency to be meaningful. They could be added as an add-on-library, but IMHO we probably don't need them in the spec right now.
That sounds reassuring; I was starting to worry that the "event" concept was talking about raw events at the window-system level.
Which leads us to another big question: Concurrency. The CGA should probably be safe to use from many threads at once (although many backends aren't, so the library would have to do a lot of synchronizing). Should callbacks for multiple clicks on one button really run concurrently, or should it behave like most toolkits for other languages? I know of no "conventional" toolkits where event handlers for one event source are run concurrently. It sounds dangerous, but it might have advantages.
My gut instinct would be to allow concurrency only if the backend is
threadsafe, or if only one thread uses the GUI directly. Given the
complexity (and variability) of the control-flow in typical toolkits,
trying to manufacture thread-safety by imposing serialisation smells
like a recipe for deadlock.
--
Glynn Clements
participants (7)
-
Axel Simon
-
Daan Leijen
-
David Sankel
-
George Russell
-
Glynn Clements
-
Nick Name
-
Wolfgang Thaller