Kevin Cantu has kindly reported several bugs for my reactive-banana library. One of them is a particularly vexing issue about external events that occur concurrently. I’m a bit stumped on the question of choosing right semantics, so I’m writing this blog post to spark a small discussion; feedback of any kind is welcome.
UPDATE: Many thanks to everyone for discussion this issue, I have found a solution that I’m happy with!
The problem itself is orthogonal to functional reactive programming, so let us imagine some generic, imperative event-based framework, for instance a GUI framework like wxHaskell or Gtk2Hs. You may also imagine Node.js, with the reservation that I know almost nothing about it.
The main question is: how do you deal with events that occur while you are currently handling another event? More concretely, consider the following minimal example program written using wxHaskell:
(Here a link to the source code in case it does not display.)
It’s quite long for a minimal example, so let me explain. The two main widgets on the screen are a button and a text entry box, which has the focus. When you click the button, the text entry box will be disabled. However, this will also cause it to lose focus, which will trigger a corresponding “lose focus” event.
The question is: when do you handle the “lose focus” event? If you handle it while the text entry is being disabled, you may violate internal invariants. This is exemplified by the variable
invariant, which is set to
False while the button click is handled. Clearly, this means that handling a button click is not atomic. (In the case of the reactive-banana library, this would screw up library internals; hence the bug report.)
In short: reacting to an event while handling another one may expose internal invariants.
But if you queue the “lose focus” event and handle it after the button click, you may violate external invariants. In this case, the external invariant is that a disabled text field cannot generate “lose focus” events; after all, that would be silly.
In short: reacting to an event after finishing another one may render it “impossible”, i.e. it should not have happened in the first place.
(A useful idea for reactive-banana would be to make the “lose focus” event happen simultaneously with the button click, but we’ve already performed too much IO for this to be viable. Once a single side effect is executed, there’s no way back.)
Do you, dear reader, have any ideas of how to resolve this problem?
I’m currently stumped on this. A small consolation is that the example demonstrates several interesting things: