# Object-Oriented Haskell

## Part 3: Generalized Mutability

## Nov 21, 2017

In Part 1, we have seen how an interface-based OOP model can be implemented in idiomatic Haskell, and we then extended this model to allow for mutable objects by using `IORef`

in Part 2. In this part, we will extend the model further to decouple the mutable-variable implementation from mutability in the objects themselves.

## Generalizing Mutable Variables

Let’s look at the bare minimum interface for mutable variables. We need the following building blocks:

- A type for our mutable variables; since we want variables of all sorts of payload types, this is going to be a type of kind
`* -> *`

. If we’re going to use`IORef`

s , then`IORef`

is that type, and it does have the right kind. Let’s use a completely arbitrary type variable name for this type,`v`

. - A type to represent effectful computations that involve mutable variables. This type, too, is going to be of kind
`* -> *`

, and it is going to somehow be linked to the`v`

type. We will pick another entirely arbitrary type variable,`m`

. For`IORef`

s, this type is going to be`IO`

. - A function to
*create*a new variable:`newVar :: a -> m (v a)`

- A function to
*read*a variable:`readVar :: v a -> m a`

- A function to
*write*a variable:`writeVar :: v a -> a -> m ()`

We can implement these primitives for `IORef`

like this:

```
newVar :: a -> IO (IORef a)
newVar = newIORef
readVar :: IORef a -> IO a
readVar = readIORef
writeVar :: IORef a -> a -> IO ()
writeVar = writeIORef
```

Let’s look at another mutable-variable type: `TVar`

.

```
newVar :: a -> STM (TVar a)
newVar = newTVar
readVar :: TVar a -> STM a
readVar = readTVar
writeVar :: TVar a -> a -> STM ()
writeVar = writeTVar
```

Obviously the `m`

type for `TVar`

is `STM`

.

## Mutability Typeclasses

The above means that we will want the three functions there to be polymorphic over `m`

and `v`

. The idiomatic way of achieving that in Haskell is to use a typeclass; since two types are involved here, we are going to need the `MultiParamTypeClasses`

extension here. So:

```
class MutableVars v m where
newVar :: a -> m (v a)
readVar :: v a -> m a
writeVar :: v a -> a -> m ()
```

This works, but we will take a slightly different approach: instead of one typeclass, we will introduce two of them:

```
class ReadVar v m where
readVar Write:: v a -> m a
class WriteVar v m where
newVar :: a -> m (v a)
writeVar :: v a -> a -> m ()
```

In practice, we will address two additional concerns: 1. The `v`

type is not generally going to be mentioned in our method signatures, which means that we need to somehow tell the compiler how to infer `v`

from a given `m`

. 2. Whenever we are going to write to a variable, this will generally also involve reading from variables, either the same one or another.

Issue 2 is easy to address: we will simply use subclassing to make sure that `WriteVar`

always implies `ReadVar`

. So:

```
class ReadVar v m where
readVar Write:: v a -> m a
class ReadVar v m => WriteVar v m where
newVar :: a -> m (v a)
writeVar :: v a -> a -> m ()
```

GHC has two extensions that can both solve the second problem: `FunctionalDependencies`

(a.k.a. fundeps), and `TypeFamilies`

. Using fundeps, this is what the typeclasses end up looking like:

```
class ReadVar v m | m -> v where
readVar Write:: v a -> m a
class ReadVar v m => WriteVar v m | m -> v where
newVar :: a -> m (v a)
writeVar :: v a -> a -> m ()
```

For those unfamiliar with fundeps, the `m -> v`

part means “the choice of `v`

depends on the choice of `m`

”, or, put differently, “the choice of `m`

determines the choice of `v`

”. Meaning that once we have written one instance for any combination of `m`

and `v`

, we are not allowed to write another instance that involves the same `m`

.

With this functional dependency in place, we can specify just the `m`

and have the compiler infer the correct `v`

from it.

And now we’ll write instances:

```
instance ReadVar TVar STM where
readVar = readTVar
instance WriteVar TVar STM where
newVar = newTVar
writeVar = writeTVar
instance ReadVar IORef IO where
readVar = readIORef
instance WriteVar IORef IO where
newVar = newIORef
writeVar = writeIORef
```

## Mutable Objects Generalized

Armed with the above, let’s generalize our mutable objects and interfaces, starting with the `Renderable`

interface:

```
data Renderable v
= Renderable
{ render :: forall m. ReadVar m v => Renderable v -> Graphics -> m ()
}
```

The `Label`

type also needs to be generalized:

```
data Label v
= Label
{ labelPosition :: v Position
, labelText :: v String
}
newLabel :: (Applicative m, WriteVar v m)
=> Position
-> String
-> m (Label v)
newLabel position txt =
Label <$> newVar position
<*> newVar txt
instance (Label v) `Is` (Renderable v) where
cast label =
Renderable
{ render = \this g -> do
position <- readVar (labelPosition label)
txt <- readVar (labelText label)
drawText g AlignLeft AlignBaseline position txt
}
```

## Conclusion

We have achieved two important goals here.

First, neither the `Label`

type, nor the `Render`

interface, nor the `Render`

implementation for `Label`

, mention `IO`

or `IORef`

anywhere, they are completely expressed in terms of the abstract `ReadVar`

and `WriteVar`

typeclasses. We have decoupled mutability semantics from mutability implementation. And this means that we can now use them in an `STM`

context without further ado - at least if we also generalize our drawing primitives:

Second, by splitting up the mutability typeclass into “read-only access” and “read-write access” typeclasses, we can declare some methods as “read-only”, and as long as our `ReadVar`

instances are lawful, it will be impossible to implement them such that they mutate anything. At the same time, if we want to call such a read-only method from a read-write context, we can, because every read-write context is also a read-only context (because `ReadVar v m => WriteVar v m`

), very much similar to how `const`

works in C++.

By the way, 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.