
On Wed, 19 Mar 2003 14:51:31 +0000
Alastair Reid
The discussion of the cost of receiving mouse events seems to provide an argument against using streams of events as the base on which other APIs are implemented.
I think that neither me nor G.R. were proposing this, we wanted to include the feature, among other ones.
(A stream-based system could have operations to turn certain events on and off but this leads to a difficult programming style where you have to coordinate your pure event-stream amnipulation code with your monadic event on/off code.)
Not another e-mail about streams, everybody don't worry; I want to show with a simple example my personal views on the following: 1. how to let different approaches to "time-varying values" cooperate, and how to choose the one wich is appropriate 2. why local state handling must be well designed, and can't be dismissed with an MVar or similar; we could need more features. I might say what is already obvious for other people. In this case, just tell me. I never feel offended by the truth :) [a note, after finishing the post: it took me more than an hour to write this in english... ] SPECIFICATION I want a program that shows a blackboard. Users can press a button to draw onto the blackboard, and another one to stop drawing (perhaps to become able to scroll the blackboard without risking to acidentally modify it). The drawing is persistent on a file and/or synchronized with another instance running elsewere on the net. To draw onto the blackboard, when drawing is enabled, the user keeps the mouse pressed. Once the mouse is released, the curve is interpolated to reduce human errors. Each press of the mouse button starts a new curve. IMPLEMENTATION Now, here's a possible implementation wich could be easy to code, and also lead to readable code, wich is one of the main features of haskell. The major need for implementation is a datastructure wich: - make state changes in mutual exclusion - allows imperative get/set features. - allows state changes to be watched, both with callbacks and streams M.C. Ports libary, whose name I already abused here, does the job. *** 1. The buttons The "draw" button has a callback, which enables the "mouse motion" and the "mouse button 1" callbacks, and writes in a stateful datastructure the IO action needed to unregister them. The "stop drawing" button also has a callback wich unregisters the callbacks. Why do I choose a callback over a stream, to do these actions? It's simple, and I guess that this is the case that people always think about, when they manifest open skepticism to streams in GUIs: the buttons always do the same thing (or quite, it alternates two behaviours); what would I do if I wanted to use a streams of clicks? I would just use a mapM_ over the list, so I ***would be just simulating callbacks***! Why should I do it? Note n. 1: There are TWO buttons. Using ONLY streams would mean to have the streams recurse each other, wich is HARD TO CODE, and HARD TO READ and understand, in my view. *** 2. Drawing the contents on the blackboard on the screen To draw onto the blackboard, I keep a list of curves, built somehow by interpolating the points hit in each draw. This list is in a state variable. I install a callback wich is triggered by state changes, wich draws the entire blackboard onto the screen (of course, optimizations could be done). I choose a callback for the same reason given in (1.) *** 3. Drawing onto the blackboard Of course, here I use streams. I merge (somehow, everybody can imagine how) the mouse position stream and the mouse button stream, and can obtain, with a rather standard function on lists, the list of lists of all the points drawn by the user in each single curve, i.e. the list of all the curve data. Then I map the interpolation function on it and put each curve into the global state. Why do I choose streams over callbacks? Because I am capturing a sequence of evolving states. Doing that in a callbacks means to have a shared list, wich is build from time to time. It's just *** simulating streams ***, why should I do it? I just get another shared variable, wich I hate since I am a functional programmer, and wich makes code LESS READABLE, by decoupling the code from its run over time. In general it appears to me that when you have to capture a time-evolving state in a callback based framework, you *** end up with coding a finite automata, wich sometimes is avoided by using streams. Note n. 2: Activating/deactivating of callbacks is predictable, and can be done elsewhere. It's done in the buttons callbacks. What would be the "purely streamed" approach? To afford the task of unregistering callbacks to finalization, wich means that callback could eventually never be unregistered. This is to show that compromises makes it better. (?!) Note n. 3: The kind of stream used here is one that does NOT get closed when the feeding callback is uninstalled. It just doesn't produce anything. When the callback is reinstalled, it is fed again. Note n. 4: To write state changes onto the stateful variabile of wich at point (2), I will not use event combinators, but I will use a monadic action; this is because in general, as pointed out by SPJ ten years ago, if I did not misunderstand him, using monads over requests/responses is just simpler. Again, I am combining two different approach to gather more expressive power. *** 4. Synchronizing the contents of the blackboard with a file, or a remote blackboard To synchronize the contents, one could use events or callbacks. Issues here are that generally one does not want to save the work more than say, one time per minute. This can be achieved by filtering the event with the right combinator, or by providing the right callback. I don't see advantages in one approach over another. Hope this makes sense to you Vincenzo