GUI programming in Haskell "the old way"
As discussed in a previous post, purely functional GUI frameworks may fail to deliver in terms of feature coverage, look-and-feel and codebase scalability. As a result, many programmers turn back to good-old Gtk+ for their user interfaces (see, for instance, Ian-Woo Kim’s hoodle).
But programming using gtk2hs (the haskell library that makes Gtk+ available) has important implications, most of them consequences of using unsafe calls wrapped in IO values as part of monadic IO blocks.
In this post we will go through the possible consequences of writing IO code, how Gtk+ enforces such style, how it affects our application architecture at large, and how reactive programming can help.
The “problem” with monads
There is nothing wrong with writing monadic code. In many cases (including sequential, order-dependent state transformations) it is the right thing to use. But ideally, we want to escape the IO monad for two reasons:
IO code can have side effects, and that means it is not possible to optimise the code (both by the compiler and by programmers) in the same way. In particular, one needs to understand the possible side effects of any operation in order to substitute it for another (even if both return the same result). The same is not true if the functions are pure.
While writing monadic code can be intuitive in some cases, a step by step execution is only one of the many possible ways to describe a solution, and often not the most intuitive one. Step by step instructions often make it harder to understand the meaning of the (mathematical description of the) calculation behind the algorithm (check Bird’s Pearls of Functional Algorithm Design or Matrix operations in game programming and image recognition to understand why that is true).
Unfortunately, UI libraries are wrapped in IO APIs, so at least at a first glance, it seems we will be stuck in imperative programming style for some time (even it it is Haskell).
GUI programming in the IO monad
Gtk+ applications written in C are surprisingly low-level. If you’ve been a haskell programmer for a while, give a try to the following program: https://github.com/abstrakraft/cwiid/blob/master/wmgui/main.c). I cannot say anything bad about the program itself: it is well-written, it does what it’s supposed to, and I use it often. I do not critisise the program or the programmer, but the language: there is a lot of boilerplate code there to install signal handlers by hand, GUI initialization, operations that update both the UI and the internal application model, etc.
But if we go to the Haskell land, we see that things do not necessarily look much better. Take a look at Ian-Woo Kim’s implementation of hoodle (again, I am not critisising Ian or his code, I know him personally and I think the problem is in the tools we are using, not someone’s particular programming style): https://github.com/wavewave/hoodle/blob/master/hoodle-render/src/Graphics/Hoodle/Render.hs#L235. It’s definitely not easy to understand what is going on (and much of it is Cairo code, which is supposed to be a relatively clean interface).
So, we can see that the aforementioned problem of understanding monadic code is present in Haskell too. In order to improve it, we would need the same tools we use in imperative programming (breaking procedures in very small chunks, adding comments, etc.). But still, the boilerplate would hardly go away. And there’s one more problem intrinsic to the MVC architecture.
MVC is the king of the visual application architectures. It has dominated the visual arena for decades, it is still taught at CS courses worldwide, and it is key in web frameworks such as Ruby on Rails. It holds only a handful of worthy contestants, some of which address the major concern that we will discuss later on.
Maybe the most annoying aspect of MVC is that the controller (that subsystem that keeps the view and the model in sync and reacts to user input) knows everything. And I literally mean E-VERY-THING. Not only does it react to input, it knows which parts of the model (internal, conceptual problem representation that we manipulate to achieve a solution) change depending on which parts of the model are touched. It also knows which parts of the UI need to be updated for minimal updates (nobody likes the screen to blink with a full refresh every time we press a key). At every change and with every input, in needs to adapt the model and refresh only those parts of the view that need refreshing, which requires a fine-grained list of updating operations, often not worth describing separately. The result: the controller knows everything that is ever going on, does most of the logic, and updates exactly those parts of the UI that actually have changed.
This is annoying, and a lot of unnecessary work, and poor design. It doesn’t scale well either: imagine that we add one more aspect to our model. Now we need to go operation by operation, trying to find those event handlers that might affect it, and manually propagating changes to the view. If feels that we can do better than that. But first, let’s try to state the problems very clearly:
The controller knows about the internals of the model and the view. (Coupling, separation of concerns.)
The controller needs to update the view with every change to the model. This is often done on a change-by-change basis. (Code repetition, coupling.)
Much of the problem solution is moved to the controller, an effectful (IO-dependent) component, when most of it belongs in the Model. But because we need that knowledge about which parts of the model have been modified, we need the controller to know what’s going on. And so, the controller does most of the job. (Poor structure, invariant keeping, code reusability.)
That controller cannot be plainly reused if we want to turn our application into a web-server application, or a mobile application. We would need, instead, to rewrite the logic intertwined with a new UI layer. So much for MVC, right?
Meet Reactive Programming
Reactive programming is a paradigm in which values can change over time and a change to one value triggers an update of other values (that depend on it). Sounds familiar? While the Functional realization is based on signals, plain OO and imperative implementations tend to be much more straightforward: you can observe any change. Much in the way that we can install even handlers for UI widgets and detect when visual elements change, we can install event handlers in our model, and detect changes to any part of our program. This does not solve all the problems stated above, but it does solve much of it. Now, if a controller, as part of some view event handler, modifies a part of the model and that provokes a change that affects a completely different part of the view, someone else will take care of it. There’s no need to manually update, because another event handler will take care of that. In that sense, it turns every old controller operation (event handler) into a uni-directional actuator and data propagator. That, of course, is not necessarily so, but it gives (and encourages) such structures.
Reactive programming also gives us the possibility of moving logic into the model: as long as we trigger the right change notifications (or event handlers) after a change to the model, it can take care of more complex operations without the controller. To the old imperative programmer, It almost feels like we are stealing from the controller, but that logic does belong in the model. It is where it should be. Later we will see where to draw the line (which is more of an art or a craft than an exact science, but we will try anyway).
In the next post we will see how to implement a non-functional Reactive Programming layer in our programs. And we will see that reactivity, as proposed, is not enough, but it enables a whole range of changes that will let us specify data dependencies in a much more declarative way.