Delving into Reactive Values
In a previous posts we examined the current state of GUI programming in Haskell, how imperative libraries get in the way of functional beauty, and how (non-FRP) reactive programming enables short, declarative code and facilitates code reuse. But without the low-level details, without more information on how this framework is structured, all we have is an idea, a draft on a piece of paper. It’s impossible to know how much effort it will require, or how much it will affect our code.
Writing reactive programs also enables a whole new range of code refactorisations, libraries and orchestrations. In this post we will explore the internals of Keera Hails, the reactive framework, and in the next we will see how it enables extremely short programs that incorporate a wide range of features.
Reactive values: the implementation
Reactive values are implemented, at a low level, as two layers:
A set of classes that describe reactive capabilities.
A set of types that describe a default implementation.
The set of classes capture whether a variable can be read, written or both. With a variable that we write to, there’s very little we can do. But with a variable that we read, we also need to know when we can read it.
The functions that reactive values must overload are:
These correspond, one by one, to the well known
(also known as
notify). In fact, the types actually correspond, for a
a carrying values of type
a -> Getter b,
a -> Setter b and
a -> Notifier b, respectively. In the current implementation
all three are IO computations: Getter returns a value, Setter gets a value and
returns nothing, and notifier gets a computation and returns nothing.
Data transfers are done the obvious way, so we will only present one case here:
(=:>) :: (ReactiveValueRead a b, ReactiveValueWrite c b) => a -> c -> IO () (=:>) v1 v2 = do reactiveValueOnCanRead v1 sync1 where sync1 = reactiveValueRead v1 >>= reactiveValueWrite v2
That is, whenever the value can be read, we read it from one reactive value and transfer it over to write it in the writeable reactive value. Note that the type signature uses classes, not explicit types, so that this rule can be applied to read-only/write-only/read-write values.
From widgets to reactive values
Gtk+ widgets are particularly easy to turn into collections of reactive values. But contrary to the approach used in Fudgets, we are going to turn each attribute, each procedure/method, and each property into either a field on its own or one of the three methods given above for some existing field. Although it can be done for any attribute relatively easily, we are going to give here the hardest case: when we have to spell out all three overloaded definitions:
scaleValueReactive :: RangeClass a => a -> ReactiveFieldReadWrite Float scaleValueReactive e = ReactiveFieldReadWrite setter getter notifier where getter = fmap double2Float $ get e rangeValue setter v = set e [ rangeValue := float2Double v ] notifier p = void (on e valueChanged p)
And that is all. That makes it possible to turn a scale’s value into a reactive value. Not that it works on a type class, so we do not need to write custom widget field reactive ``exporters’’ for each widget type (this is not due to our library, but due to the fantastic job done by the gtk2hs team, who wrote one class for each kind of widget in Gtk+’s hierarchy, enabling some sort of inheritance).
toggleButtonActiveReactive :: ToggleButtonClass t => t -> ReactiveFieldReadWrite Bool toggleButtonActiveReactive e = ReactiveFieldReadWrite setter getter notifier where getter = toggleButtonGetActive e setter = toggleButtonSetActive e notifier = void . (on e toggled)
From models to reactive values
As it turns out, the work necessary to turn a pure mathematical model into a reactive value is slightly harder. In our case, we will work only with records and similar definitions, but this would require some manual work for custom operations.
The implementation of Hails wraps all models in two layers: a reactive layer, and a protected layer. The reactive layer takes care of tracking which elements have changed and which notifiers need to be executed. The problem is that, if our application uses threads, we may get all sorts of strange effects. In order to make everything slightly simpler, we have also created a concurrency layer, which defines what we call a protected reactive field. A protected reactive field is one that can only be accessed in a thread-safe manner. Because fields can affect other fields, by default we block the whole model, perform the complete change, and then free the model again.
This seems like a lot of work, but happily, there is a way to automatise it.
Keera Hails uses Template Haskell to generate the definitions of reactive and protected fields for a record field. It only needs to know the name of the program model, the name of the field, its type, and the type that represents all events. One example taken from the code of Keera Posture follows:
protectedField "Language" [t|Maybe Language|] "Model" "ModelEvent"
As you can see, that really is not a lot of code. In future versions Model and ModelEvent will be removed, lenses will be used, and hopefully we will not need to use this explicit extra layer at all.
In the next blog post we will explore the tools that form the Keera Hails infrastructure, which include a multitude of libraries and code generators.