[GUI] [Long] State handling, if this already matters

Vincenzo Ciancia ciancia@cli.di.unipi.it
Thu, 20 Mar 2003 23:11:29 +0100


On Wed, 19 Mar 2003 14:51:31 +0000
Alastair Reid <alastair@reid-consulting-uk.ltd.uk> wrote:

> 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