Tobias Dammers

programming

Object-Oriented Haskell

Part 2: Mutability And Multiple Interfaces

Nov 1, 2017

In Part 1, we have seen how an interface-based OOP model can be implemented in idiomatic Haskell; we defined interfaces, a typeclass to enable a typesafe cast-to-interface function, and an accessor function / operator to conveniently access an object member through an interface. We will now look at how we can implement mutable objects in idiomatic Haskell.

Mutability in Haskell

Haskell is a pure functional programming language, and part of that is that dealing with mutability requires special attention. A lot has been written about this general topic already, so I’ll skip over the details.

The first approach a Haskeller would typically take when presented with a problem that is inherently stateful is simple: Functions. More specifically, endofunctions (functions where the input and output types are the same):

Some might prefer state monads, which are really just a very very thin abstraction layer over the same mechanism:

Unfortunately, this won’t cut it. We will see later why exactly that is.

But we can step it up: If we accept moving our methods to IO return types, then we get access to IORef, a simple mutable-variable type. It doesn’t provide much in terms of thread safety beyond making individual updates atomic, but for the purpose of this post, this is good enough. Here’s what IORef usage looks like:

If we do want better threading support, STM (Software Transactional Memory) and its mutable variable primitive TVar are a good idea:

We will however use IORef for now. Generalizing mutability is going to be a topic in a future post.

Mutable Fields With IORef

Using IORef for our mutable fields has a few consequences. First, because our fields are now mutable, anything that accesses them has to live in IO. This means that our interfaces also have to have methods in IO, otherwise they cannot read data from mutable fields. But when we define an interface, we do not want to dictate whether data is read from a mutable field or not, so once we start supporting mutability at all, it’s best to have all interface methods live in IO. This is unfortunate, because it means that we give up immutability guarantees; but we will address this concern later, introducing some simple type system tricks that will buy us some guarantees back.

Another consequence is that object construction now also has to happen in IO, because that’s where we have to create our IORefs. In practice, this means we will be writing at least one IO function for each of our types, and that function will essentially play the role of a constructor (in the OOP sense, not the Haskell sense).

Case Study: A GUI System

Let’s put the above in practice: We’re building a classic event-driven GUI, consisting of a main loop and a collection of composable components. First, let’s jot down a quick outline of what the main loop will look like:

I’m not showing the definition of Graphics; we’ll assume that it is an opaque type provided by a suitable library that allows us to render all sorts of graphics primitives and represents a GUI context like a window or a canvas. Usages that appear in this post should be self-explanatory.

I’m not showing implementations of eventSource here either, but it’s easy to imagine what it might look like: a naive implementation could just repeatedly poll all inputs, and return a suitable Event as soon as any of them produces anything, while a more sophisticated implementation would probably be multi-threaded and use some sort of thread-safe channel to move events around. And, speaking of events, here’s what the Event type might look like:

Note that we’re using a plain algebraic data type here: we don’t use OOP here, because we don’t need (nor want) extensible runtime polymorphism. Naturally, a real-world GUI would need a much richer event type. We’ll get back to events in a minute though.

Rendering

The main loop tells us what kind of interface (or interfaces) our component must support: there must be a render method, and a handleEvent method. Let’s start with render:

Let’s write some components and their Renderable implementations. Starting with a very simple one: the static label.

Great. Only slightly more elaborate: Buttons.

OK, so now we can render our crude components. But a button isn’t a button if we can’t click it, so…

Handling Events

First attempt:

And let’s provide a default implementation that our real implementations can use as a template:

That is, the default implementation dispatches events according to their constructor in handleEvent, and provides “do-nothing” defaults for all the individual handlers. This means that we can override just the methods that interest us, and leave the rest at their defaults.

So here’s how we implement EventHandler for our two component classes:

In practice, we might instantiate a button like so:

Multiple Interfaces

One problem though. We have defined two interfaces, but we want to pass in one value that implements both. We could of course change the type of our runGUI function like so:

It works, because our (==>) operator automatically resolves to the right interface through the Is typeclass. It’s not ideal though, and I will show you why.

But first, let’s build a feedback mechanism into the handleEvent method: we will change the return type from IO () to IO Accepted, like this:

This is useful, because we need our GUI to be compositional, that is, we want to compose complex GUIs from simple building blocks, and part of that will involve dispatching events to multiple components. For that to work nicely, we need a way to tell whether a component has accepted an event or not: if it has, we consider it handled and stop, but if it hasn’t, we try the next component in line. Here’s such a component group type:

And this is where our first approach to multiple interface types breaks down: if we put both the `Is` Renderable and `Is` EventHandler constaints on the component list here, and make it [component], we don’t get a heterogenous list - all list elements must be of the same type, because that is how Haskell’s type system works. So we need to move the “must be both Renderable and an EventHandler” constraint to the term level, just like we did with individual interfaces. The solution is quite simple, actually: We simply define another interface that captures the notion of implementing both the other interfaces. The pattern is just the same as before:

And then we write boring instances for our components:

And now our ComponentGroup type will work. Some boilerplate is needed still:

And of course we need to implement the Renderable, EventHandler, and Component instances:

Now we can combine components of different underlying types into the same list; we just need to cast them.

We can also make the buttons do something other than just exit the application, such as updating labels:

Conclusion

At this point, we have covered much of the OOP design space, and we have managed to retain a lot of Haskell’s type goodness. Particularly, we can now express the following OOP concepts:

We’re missing some interesting features still, which I intend resolve in the next parts:

Finally: the OOP framework laid out in this blog series is also available on Hackage, under the name boop, feel free to read along and see what it looks like when you put it all together.

Revision History

  • Oct 26, 2017
  • Oct 27, 2017