This post introduces a declarative GTK+ architecture for Haskell which I’ve been working on during the journey with FastCut, a video editor specialized for my own screencast editing workflow. It outlines the motivation, introduces the packages and their uses, and highlights parts of the implementation.
Imperative GUI Programming
When starting to work on FastCut, I wanted to use a GUI framework that would allow FastCut to be portable across Linux, macOS, and Windows. Furthermore, I was looking for a native GUI framework, as opposed to something based on web technology. GTK+ stood out as an established framework, with good bindings for Haskell in the haskell-gi package.
It didn’t take me very long before the imperative APIs of GTK+ became problematic. Widgets are created, properties are set, callbacks are attached, and methods are called, all in
IO. With the imperative style, I see there being two distinct phases of a widget’s life cycle, which need to be handled separately in your rendering code:
- construction, where you instantiate the widget with its initial state, and
- subsequent updates, where some relevant state changes and the widget’s state is modified to reflect that. Often, the application state and the widget state are the same.
This programming style makes testing and locally reasoning about your code really hard, as it forces you to scatter your application logic and state over
MVars, and various callbacks, mutating shared state in
While GTK+ has the Glade “WYSIWYG” editor, and the
Builder class, they only make the first phase declarative. You still need to find all widgets in the instantiated GUI, attach event handlers, and start mutating state, to handle the second phase.
After having experienced these pains and getting repeatedly stuck when building FastCut with the imperative GTK+ APIs, I started exploring what a declarative GTK+ programming model could look like.
The package I’ve been working on is called gi-gtk-declarative, and it aims to be a minimal declarative layer on top of the GTK+ bindings in haskell-gi.
Rendering becomes a pure function from state to a declarative widget, which is a data structure representation of the user interface. The library uses a patching mechanism to calculate the updates needed to be performed on the actual GTK+ widgets, based on differences in the declarative widgets, similar to a virtual DOM implementation.
Event handling is declarative, i.e. you declare which events are emitted on particular signals, rather than mutating state in callbacks or using concurrency primitives to communicate changes. Events of widgets can be mapped to other data types using the
Functor instance, making widgets reusable and composable.
Finally, gi-gtk-declarative tries to be agnostic of the application architecture it’s used in. It should be possible to reuse it for different styles. As we shall see later there is a state reducer-based architecture available in gi-gtk-declarative-app-simple, and FastCut is using a custom architecture based on indexed monads and the Motor library.
By reusing type-level information provided by the
haskell-gi framework, and by using the
OverloadedLabels language extension in GHC, gi-gtk-declarative can support many widgets automatically. Even though some widgets need special support, specifically containers, it is a massive benefit not having to redefine all GTK+ widgets.
A single widget, i.e. one that cannot contain children, is constructed using the
widget smart constructor, taking a GTK+ widget constructor and an attribute list:
Bins, in GTK+ lingo, are widgets that contain a single child. They are created using the
bin smart constructor:
Containers are widgets that can contain zero or more child widgets, and they are created using the
container smart constructor:
In regular GTK+, containers often accept any type of widget to be added to the container, and if the container requires its children to be of a specific type, it will automatically insert the in-between widget implicitly. An example of such a container is
ListBox, which automatically wraps added children in
ListBoxRow bins, if needed.
In gi-gtk-declarative, on the other hand, containers restrict the type of their children to make these relationships explicit. Thus, as seen above, to embed child widgets in a
ListBox they have to be wrapped in
Another example, although slightly different, is
Box doesn’t have a specific child widget type, you can in regular GTK+ add children using the
boxPackEnd methods. The arguments to those methods are expand, fill, and padding, which control how the child is rendered (packed) in the box. As gi-gtk-declarative doesn’t support method calls, there is a helper function and corresponding declarative widget
boxChild to control
Box child rendering:
Note that we are using
do notation to construct adjacent
boxChild markup values. There is a monadic
MarkupOf builder in the library that the
container smart constructor takes as its last argument. Although we need the
Monad instance to be able to use do notation, the return value of such expressions are rarely useful, and is thus constrained to
() by the library.
All declarative widgets can have attributes, and so far we’ve only seen empty attribute lists. Attributes on declarative widgets are not the same as GTK+ properties. They do include GTK+ properties, but also include CSS classes declarations and event handling.
One type of attribute is a property declaration. To declare a property, use the
(:=) operator, which takes a property name label, and a property value, much like
(:=) in haskell-gi:
To find out what properties are available, see the GI.Gtk.Objects module, find the widget module you’re interested in, and see the “Properties” section. As an example, you’d find the properties available for
on attribute, you can emit events on GTK+ signal emissions.
Some events need to be constructed with
IO actions, to be able to query underlying GTK+ widgets for attributes. The
onM attribute receives the widget as its first argument, and returns an
IO event action. In the following example
getColorButtonRgba has type
ColorButton -> IO (Maybe RGBA), and so we compose it with an
fmap of the
ColorChanged constructor to get an
You can think of
onM having the following signature, even if it’s really a simplified version:
Finally, CSS classes can be declared for widgets in the attributes list, using the
In addition to the declarative widget library gi-gtk-declarative, there’s an application architecture for you to use, based on the state reducer design of Pux.
At the heart of this architecture is the
The type parameters
event will be instantiated with our specific types used in our application. For example, if we were writing a “Snake” clone, our state datatype would describe the current playing field, the snake length and where it’s been, the edible objects’ positions, etc. The event datatype would likely include key press events, such as “arrow down” and “arrow right”.
App datatype consists of:
updatefunction, that reduces the current state and a new event to a
Transition, which decides the next state to transition to,
viewfunction, that renders a state value as a
Widget, parameterized by the
Apps event type,
- inputs, which are Producers that feed events into the application, and
- the initial state value of the state reduction loop.
To run an
App, you can use the convenient
run function, that initializes GTK+ and sets up a window for you:
runInWindow if you like to initialize GTK+ yourself, and set up your own window; something you need to do if you want to use CSS, for instance.
Declarative “Hello, world!”
The haskell-gi README includes an “Hello, world!” example, written in an imperative style:
It has two states; either the button has not been clicked yet, in which it shows a “sensitive” button, or the button has been clicked, in which it shows an “insensitive” button and a label thanking the user for clicking.
Let’s rewrite this application in a declarative style, using gi-gtk-declarative and gi-gtk-declarative-app-simple, and see how that works out! Our state and event datatypes describe what states the application can be in, and what events can be emitted, respectively:
Our view function, here defined as
view', renders a label according to what state the application is in:
update function reduces the current state and an event to a
Transition event state, which can either be
Exit. Here we always transition to the
Clicked state if the button has been clicked.
Note that the
Transition constructor not only takes the next state, but also an
IO (Maybe Event) action. This makes it possible to generate a new event in the update function.
Finally, we run the “Hello, world!” application using
Comparing with the imperative version, I like this style a lot better. The rendering code is a pure function, and core application logic can also be pure functions on data structures, instead of mutation of shared state. Moreover, the small state machine that was hiding in the original code is now explicit with the
State sum type and the
There are more examples in gi-gtk-declarative if you want to check them out.
Writing gi-gtk-declarative has been a journey full of insights for me, and I’d like to share some implementation notes that might be interesting and helpful if you want to understand how the library works.
At the core of the library lies the
Patchable type class. The
create method creates a new GTK+ widget given a declarative
patch method calculates a minimal patch given two declarative widgets; the old and the new version:
A patch describes a modification of a GTK+ widget, specifies a replacement of a GTK+ widget, or says that the GTK+ widget should be kept as-is.
Replacing a widget is necessary if the declarative widget changes from one type of widget to another, say from
ListBox. We can’t modify a
Button to become a
ListBox in GTK+, so we have to create a new GTK+ widget and replace the existing one.
Declarative widgets are often wrapped in the
Widget datatype, to support widgets of any type to be used as a child in a heterogeneous container, and to be able to return any declarative widget, as we did in the
App view function previously. The
Widget datatype is a GADT:
If you look at the
Widget constructor’s type signature, you can see that it hides the inner
widget type, and that it carries all the constraints needed to write instances for patching and event handling.
We can define a
Patchable instance for
Widget as the inner widget is constrained to have an instance of
Patchable. As the
widget is also constrained with
Typeable, we can use
eqT to compare the types of two
Widgets. If their inner declarative widget types are equal, we can calculate a patch from the declarative widgets. If not, we replace the old GTK+ widget with a new one created from the new declarative widget.
Similar to the case with
Patchable, as we’ve constrained the inner widget type in the GADT, we can define instances for
At first, it might seem unintuitive to use dynamic typing in Haskell, but I think this case is very motivating, and it’s central to the implementation of gi-gtk-declarative.
Smart Constructors and Return Type Polymorphism
All smart constructors —
container — can return either a
Widget value, such that you can use it in a context where the inner widget type needs to be hidden, or a
MarkupOf with a type specifically needed in the contexts in which the widget is used, for example, a bin or container with a requirement on what child widget it can contain.
Here are some possible specializations of smart constructor return types:
widget Button  :: Widget MyEvent widget Button  :: MarkupOf (SingleWidget Button) MyEvent () bin ScrolledWindow  _ :: Widget MyEvent bin ScrolledWindow  _ :: MarkupOf (Bin ScrolledWindow Widget) MyEvent () container Box  _ :: Widget MyEvent container Box  _ :: MarkupOf (Container Box (Children BoxChild)) MyEvent ()
As a small example, consider the helper
textRow that constructs a
ListBoxRow to be contained in a
As the type signature above shows, the
textRow function returns a
MarkupOf value parameterized by a specific child type:
Bin ListBoxRow Widget. You can read the whole type as “markup containing bins of list box rows, where the list box rows can contain any type of widget, and where they all emit events of type
Event.” I know, it’s a mouthful, but as you probably won’t split your markup-building function up so heavily, and as GHC will be able to infer these types, it’s not an issue.
The return type polymorphism of the smart constructors should not affect type inference badly. If you find a case where it does, please submit an issue on GitHub.
Callback-centric GUI programming is hard. I prefer using data structures and pure functions for core application code, and keep it decoupled from the GUI code by making rendering as simple as a function
State -> Widget. This is the ideal I’m striving for, and what motivated the creation of these packages.
I have just released gi-gtk-declarative and gi-gtk-declarative-app-simple on Hackage. They are both to be regarded as experimental packages, but I hope for them to be useful and stable some day. Please try them out, and post issues on the GitHub tracker if you find anything weird, and give me shout if you have any questions.
The gi-gtk-declarative library is used heavily in FastCut, with great success. Unfortunately that project is not open source yet, so I can’t point you to any code examples right now. Hopefully, I’ll have it open-sourced soon, and I’m planning on blogging more about its implementation.
Until then, happy native and declarative GUI hacking!