On the state of GUI programming in Haskell

GUI programming in functional and logic languages tends to feel quite hard. There is nothing intrinsic in FP that makes GUI programming extra hard, but when everywhere else  you are surrounded by nice, abstract, compositional, high-level descriptive definitions, working at the GUI’s low level feels… well, like you’re doing something wrong.

In the case of Haskell, for instance, and by using the IO monad and MVars, we know that code can be, at least, as good (or as ugly) as traditional imperative code. (The only additional difficulty might be, if at all, reasoning about IO and lazy evaluation, but the cases in which that presents an actual problem are rare).

GUI programming revolves around the idea of the widget (or gadget or visual element), which has internal state, responds to user events, presents some information, and takes “ownership” of a certain area of the screen. Widgets are placed on the screen in positions that depend on those of other widgets, and when one of them is affected by a user event,
several other widgets can see their states changed. Being such a state-driven transformation, GUIs have the word monad written all over them. But not all is bad…

When doing GUI programming in Haskell, there are two (main) options.

– To use some binding to an imperative library. There are elegant, wide-spread, imperative solutions for GUI programming, and some of them have corresponding binding libraries. We are talking about, of course, Gtk+, WX and Qt.

– To use a more functional or higher-level API. We can easily draw a line between the family of FRP frameworks with GUI backends and other functional frameworks.

Let’s go through them one by one.

Imperative GUI Programming Libraries

Currently in Haskell there are a few binding libraries that allow us to use existing GUI toolkits. The most common are Gtk+ (via gtk2hs), wx (via wxHaskell) and Qt (via qthaskell).

  • Gtk+ is, by far, the best supported. It gives a natural interface in all OSs. There is plenty of documentation, and many advanced features are well supported. Installation is easy in all OSs. The results look professional (and we have been using this in production with our clients). On the not-so-bright side, distribution of compiled Gtk+-dependent programs requires carrying a lot of dlls.
  • WX is an elegant, cross-platform approach that uses the OSs “native” windowing system. It is not as well-supported as Gtk+: installation, even in Linux systems, is much harder. There is a diagreement between the dependencies of the last version available on hackage and the latests wx library available on debian/ubuntu, making it necessary to download and install dependencies by hand. The obvious solution, reverting back to wxhaskell 0.11, will leave you with a relatively old GUI toolkit. In conclusion: an elegant, clean approach with very few dependencies, but not nearly as well supported as Gtk+.
  • QT is, by far, the most elegant and feature-rich of all three. Unfortunately, and to the extent of my knowledge, nobody has been actively working on qthaskell for more than 5 years, so the bindings are pretty outdated. As a result, installation (with a relatively recent version of ghc and cabal) is impossible.

Even when one takes care of writing clean code, these libraries leave a very monadic aftertaste that affects the whole program. Also, and as we will see, this approach has consequences in terms of the application architecture, and we need to take that into consideration if we want our codebase to scale well. Before we delve deeper into those two problems, let’s look at the alternatives.

Writing GUIs with functional libraries

So, let’s imagine that we don’t want to write impertive-looking code, and we want to create some compositional systems (building larger systems in terms of smaller systems). As it turns out, there are a range of more functional alternatives for the pure haskeller. Most of them are based on some form of Functional Reactive Programming, although, at least for the sake of covering other views, we will also present other existing purely functional approaches.

  • There are some functional GUI toolkits that represent widgets as functions. That includes, for instance, Fudgets. We may write a post of Fudgets in the future, but there are two main practical issues with Fudgets: 1) it is incredibly outdated, so even if the ideas are sound, nobody has maintained it, and 2) fudgets are widgets with a single type, meaning that each widget has a type (and only one) that it manifests. That last point makes Fudgets not fine-grained enough for realistic purposes.
  • Functional Reactive Programming (FRP) has long been regarded as the purely functional way of representing large networks of interdependent visual elements. All variants are based, in one way or another, on the idea of signals, which represent continuously varying values. Because any property can only have one value at a time, it becomes easy to (conceptually) represent signals as functions from time to value. giving us the purely functional approach that we were looking for. Examples always demonstrate a straightforward reading of signal definitions, but realisations have important consequences in terms of efficiency and program architecture, that will be explored below.

Functional Reactive Programming

FRP is often advertised as presenting the following benefits:

  • No need to explicitly handle time sampling. One can easily define signals (or transformations between signals) ignoring time altogether. These will later be applied pointwise (or rather, sample-wise).
  • No need to explicitly pass on changed values. If a signal depends on another, a change to the value of the latter will provoke a change to the value of the former. (What this really means and how it can be realized, we will see later on).
  • Every signal has only one definition. So, for any misbehaving varying element in our application, we need only look in one place to find out where the problem might be.

It’s difficult to argue that these properties are not true. It might be easier, however, to explain why they are not always beneficial, and how, in some applications, the cost of reasoning in terms of Signals and Signal Functions is not worth the effort.

FRP applied to Large Applications

Many programs with GUIs (what we call “applications”) turn out not to have any continuously changing element. Most changes respond to user events (hence the success of event-oriented programming in past years), and many applications do not even have frequently-changing elements.

Some FRP frameworks are built around this idea of event-based programming (eg. reactive-banana), but others (e.g. Yampa) refresh applications either at regular intervals or as frequently as possible. Implementing desktop applications with rarely-changing GUIs using regular or constant refreshes turns out to be an overkill. It might make sense to use Yampa or a similar framework if we really need to paint as frequently as possible, but if the appearance does not change on its own and our input devices are sampled with low frequencies, it might be much better to retort to a more discrete approach.

A second problem with FRP is that, contrary to popular belief, large applications might not benefit so much from such definition-uniqueness property. In larger applications, different changes to the same widgets will correspond to different features. Putting everything in one definition forces us to explicitly name and combine the values in a precise, clear way, which results in increased precision (benefit) and coupled code (disadvantage).

The situation becomes more problematic when we have interdependent elements, since there are a limited number of ways in which we can specify loops in a purely functional sound way. In most cases, we will be forced to give the definitions of interdependent elements together (in the same module and/or function), which only increases coupling.

A final problem inherent to both FRP and Fudgets is that programmers tend to use the widgets’ state to store the information about the problem they are trying to solve, therefore relying on a very simple type system, and forgetting the power that the programming language (be it Haskell, Idris, etc.) gives them to capture the ideas they are trying to address in the most precise manner.

In a future post we will explore how gtk2hs applications can be architectured, the scalability problems that such architectures can have, and what we can do about it.

Please, let your comments and/or request explanations below.

10 Comments

    1. Anonymous

      How does QML hold against Qt performance/features-wise?

      I’ve been eyeing QML for a while, but intuition tells me a native C++ solution must outperform a solution with a java-script layer… It’s better than nothing, but is it really the same thing?

  1. How does QML hold against Qt performance/features-wise?

    I’ve been eyeing QML for a while, but intuition tells me a native C++ solution must outperform a solution with a java-script layer… It’s better than nothing, but is it really the same thing?

  2. Joco

    “Putting everything in one definition forces us to explicitly name and combine the values in a precise, clear way, which results in increased precision (benefit) and coupled code (disadvantage).”

    Could you explain this in more detail? If FRP has this problem, what is better? Callbacks?

    1. Obviously, callbacks are *not* the solution :)

      In FRP, signals are defined in terms of other signals.

      Let’s say that you want to define two signals, one that represents a live text-box in the UI, and another that represents a project name in the model.

      The signal in the model needs to be updated when the view changes, so it is defined in terms of the signal in the view. The converse is true also for the textbox signal, which needs to have the project name (model) signal in scope in order to be defined. So, to define two (partly independent) signals, one of which is stored in the model, and another that is visualized in the view, they need to be in scope of one another. So you have no choice but to define them together (same module, and possibly same function). That results in poor modularity, but absolute precision: you always know what the value of each signal will be. It’s just that they do not really belong together so well, even if they happen to be partly related.

      My article at the last Haskell Symposium explains how this could be a problem in a program like PDF reader, in which several GUI elements allow interaction with the model, and everything needs to be defined in the same place.

      An alternative, which I am studying at the moment, is Reactive Values. While I have used reactive values for large-ish (order 20K locs) GUI programs, they currently lack support for dynamism and precise denotational semantics. I’m currently working on that.

  3. Daniel

    GTK isn’t really native on Windows, the font rendering breaks when trying to render scripts that the base font doesn’t support, alt codes don’t work (there’s no good reason why it shouldn’t).

    1. Could you give an example/pointer to show how it breaks?

      While not all features may rely on the underlying native equivalent for the platform (windows in this case), GTK could choose to draw everything using a custom style (swing and tk do this, and they look quite ugly). Gtk UIs look pretty much like any other UI on windows (apart from specific differences, and unless a custom theme is used).

      What do you mean by Alt codes?

      1. programmer14

        Alt codes probably refers to the Windows feature that lets you type ASCII or Unicode characters using the numeric keypad. For example Alt-0169 in “real” Windows applications types character 00A9 (©). In Qt and Tk apps this behaviour is properly emulated, for example. But if you use Gtk your app doesn’t honour this behaviour. Other subtle details also won’t work properly in your Gtk app on Windows. For example, if you press “Alt” in a Windows program, you normally can start navigate that window’s menubar with the keyboard. But in a GTK app pressing “Alt” by itself does nothing. You must press Alt-accelerator (e.g. Alt-F for the File menu) to start navigating a specific menu or press F10 to start the menubar navigation. F10 is standard on Gnome, but not in Windows, so it’s not reasonable that a Windows user would think to press that key.

  4. Aditya Siram
    1. It’s great to know. Thanks! I wasn’t aware that these bindings were up to date or usable.

      How small would a stripped FLTK hello world haskell windows program be, including everything a non-haskell windows user would need to install to run that program (DLLs, resources, etc.)?

      And a second question: would it be easy/possible to create portable programs with FLTK, that is, compiled haskell programs (.exe) that can be unzipped and just executed from the place, without the need to install anything locally, register DLLs, etc.?

Leave a Reply

Your email address will not be published.

*