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 IORef s, MVar s, and various callbacks, mutating shared state in IO . These symptoms are not specific to GTK+ or its Haskell bindings, but are common in object-oriented and imperative GUI frameworks. If you’re familiar with JavaScript and jQuery UI programming, you have most likely experienced similar problems.

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.

Going Declarative

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.

Declarative Widgets

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.

Single 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:

= widget Button [] myButtonwidget[] = widget CheckButton [] myCheckButtonwidget[]

Note that Button and CheckButton are constructors from gi-gtk. They are not defined by gi-gtk-declarative.

Bins

Bins, in GTK+ lingo, are widgets that contain a single child. They are created using the bin smart constructor:

= myScrollArea ScrolledWindow [] $ bin[] Button [] widget[]

Other examples of bins are Expander, Viewport, and SearchBar.

Containers

Containers are widgets that can contain zero or more child widgets, and they are created using the container smart constructor:

= myListBox ListBox [] $ do container[] ListBoxRow [] $ widget Button [] bin[]widget[] ListBoxRow [] $ widget CheckButton [] bin[]widget[]

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 ListBoxRow bins.

Another example, although slightly different, is Box . While Box doesn’t have a specific child widget type, you can in regular GTK+ add children using the boxPackStart and 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:

= myBox Box [] $ do container[] False False 0 $ widget Button [] boxChildwidget[] True True 0 $ widget CheckButton [] boxChildwidget[]

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.

Attributes

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.

Properties

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:

= myButtonWithLabel Button [ #label := "Click Here"] widget = myHorizontallyScrolledWindow ScrolledWindow [ #hscrollbarPolicy := PolicyTypeAutomatic ] $ bin someSuperWideWidget = myContainerWithMultipleSelection ListBox [ #selectionMode := SelectionModeMultiple ] $ container children

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 Button here.

Events

Using the on attribute, you can emit events on GTK+ signal emissions.

= counterButton clickCount let msg = "I've been clicked " msg <> Text.pack ( show clickCount) Text.pack (clickCount) <> " times." in widget widget Button [ #label := msg #clicked ButtonClicked , on ]

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 IO Event .

data Event = ColorChanged ( Maybe RGBA ) = colorButton color widget ColorButton [ #title := "Selected color" , #rgba := color #colorSet (fmap ColorChanged . getColorButtonRgba) , onM ]

You can think of onM having the following signature, even if it’s really a simplified version:

onM :: Gtk.SignalProxy widget widget -> (widget -> IO event) (widgetevent) -> Attribute widget event widget event

Finally, CSS classes can be declared for widgets in the attributes list, using the classes attribute:

= myAnnoyingButton widget Button "big-button" ] [ classes [ , #label := "CLICK ME" ]

GI.Gtk.Declarative.App.Simple

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 App :

data App state event = state event App { update :: state -> event -> Transition state event stateeventstate event , view :: state -> Widget event stateevent , inputs :: [ Producer event IO ()] event()] , initialState :: state state }

The type parameters state and 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”.

The App datatype consists of:

an update function, that reduces the current state and a new event to a Transition , which decides the next state to transition to,

function, that reduces the current state and a new event to a , which decides the next state to transition to, a view function, that renders a state value as a Widget , parameterized by the App s event type,

function, that renders a state value as a , parameterized by the s event type, inputs, which are Producers that feed events into the application, and

the initial state value of the state reduction loop.

Running Applications

To run an App , you can use the convenient run function, that initializes GTK+ and sets up a window for you:

run :: Typeable event event => Text -- ^ Window title -> Maybe ( Int32 , Int32 ) -- ^ Optional window size -> App state event -- ^ Application to run state event -> IO () ()

There’s also 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:

main :: IO () () = do main Nothing Gtk.init <- new Gtk.Window [ #title := "Hi there" ] winnew #destroy Gtk.mainQuit on win <- new Gtk.Button [ #label := "Click me" ] buttonnew #clicked $ on button set button [ #sensitive := False , #label := "Thanks for clicking me" ] #add win button #showAll win Gtk.main

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:

data State = NotClicked | Clicked data Event = ButtonClicked

Our view function, here defined as view' , renders a label according to what state the application is in:

view' :: State -> Widget Event = \ case view' NotClicked -> widget Button [ #label := "Click me" #clicked ButtonClicked , on ] Clicked -> widget Button [ #sensitive := False , #label := "Thanks for clicking me" ]

The update function reduces the current state and an event to a Transition event state , which can either be Transition or Exit . Here we always transition to the Clicked state if the button has been clicked.

update' :: State -> Event -> Transition State Event ButtonClicked = Transition Clicked ( return Nothing ) update' _

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 run .

main :: IO () () = main run "Hi there" Nothing App = view' { viewview' = update' , updateupdate' = [] , inputs[] = NotClicked , initialState }

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 update' function.

There are more examples in gi-gtk-declarative if you want to check them out.

Implementation

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.

Patching

At the core of the library lies the Patchable type class. The create method creates a new GTK+ widget given a declarative widget . The patch method calculates a minimal patch given two declarative widgets; the old and the new version:

class Patchable widget where widget create :: widget e -> IO Gtk.Widget widget e patch :: widget e1 -> widget e2 -> Patch widget e1widget e2

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.

data Patch = Modify ( Gtk.Widget -> IO ()) ()) | Replace ( IO Gtk.Widget ) | Keep

Replacing a widget is necessary if the declarative widget changes from one type of widget to another, say from Button to 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.

Heterogeneous Widgets

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:

data Widget event where event Widget :: ( Typeable widget widget , Patchable widget widget , Functor widget widget , EventSource widget widget ) => widget event widget event -> Widget event event

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 Widget s. 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.

instance Patchable Widget where Widget w) = create w create (w)create w Widget ( w1 :: t1 e1)) ( Widget ( w2 :: t2 e2)) = patch (t1 e1)) (t2 e2)) case eqT @ t1 @ t2 of eqTt1t2 Just Refl -> patch w1 w2 patch w1 w2 _ -> Replace (create w2) (create w2)

Similar to the case with Patchable , as we’ve constrained the inner widget type in the GADT, we can define instances for Functor and EventSource .

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 — widget , bin , and 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:

Button [] :: Widget MyEvent widget[] Button [] :: MarkupOf ( SingleWidget Button ) MyEvent () widget[]() ScrolledWindow [] _ :: Widget MyEvent bin[] _ ScrolledWindow [] _ :: MarkupOf ( Bin ScrolledWindow Widget ) MyEvent () bin[] _() Box [] _ :: Widget MyEvent container[] _ Box [] _ :: MarkupOf ( Container Box ( Children BoxChild )) MyEvent () container[] _))()

As a small example, consider the helper textRow that constructs a ListBoxRow to be contained in a ListBox :

myList :: Widget Event = myList ListBox [] $ container[] mapM textRow [ "Foo" , "Bar" , "Baz" ] textRow [ textRow :: Text -> MarkupOf ( Bin ListBoxRow Widget ) Event () () = textRow t ListBoxRow [] $ bin[] Label [ #label := t ] widget

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.

Summary

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!