Technical groups
Open source
Careers
Research
Blog
Contact
Consulting services
Technical groups
Open source
Careers
Research
Blog
Contact
Consulting services

20 March 2019 — by Arnaud Spiwack

The subject of free monads is resurfacing of late, as it does from time to time. What prompted this write-up is that Sandy Maguire followed up his post with a discussion about an impredicative encoding (aka final encoding) of free monads:

newtype Freer f a = Freer (forall m. Monad m => (forall t. f t -> m t) -> m a)

That is: given a monad m, and an interpretation of my operations (f) in that monad, I can build an m a.

As it happens, capabilities-as-type-classes, as embodied by the capability library, are essentially the same thing. Let me explain.

As far as I know, the subject of impredicative encoding of free monads was first tackled, as many good things, by Russell O’Connor, who calls them van Laarhoven free monads. His blog post is a fairly mathy read. But the key bit is this:

-- (ops m) is required to be isomorphic to (Π n. i_n -> m j_n)
newtype VLMonad ops a = VLMonad { runVLMonad :: forall m. Monad m => ops m -> m a }

Where Π n. i_n -> m j_n is mathematician’s way of saying forall n. i n -> m (j n). Up to a very small difference (rename i to f and pick the identity for j), this is indeed the same type. O’Connor then proves that this type is isomorphic to the usual presentation of free monads.

data Free f a = Return a | Free (f (Free f a))

Less is more

The comment, in Russell O’Connor’s snippet, is crucial in the proof. Without it you can’t establish the isomorphism between VLMonad and the traditional Free monad.

That’s because not all effects can be represented in free monads. The prime example is exception handlers. You can make a function

handle :: Free MyEffect a -> Free MyEffect a -> Free MyEffect a

But it would have the property that (handle s f) >>= k = handle (s >>= k) (f >>= k). That is: exceptions raised after exiting the handler would still be caught by the handler. It is not a useless function, but it is not an exception handler. This phenomenon is a property of the free monad construction. In the impredicative encoding, it can be thought of as a consequence of the fact that in forall a. f a -> m a, f cannot refer to m.

So, while being isomorphic to Free is a nice theoretical property, Russell O’Connor’s phrasing presents us with an opportunity: if we simply drop the comment restricting the form of ops, we get a less constrained free monad type which supports more effects. Therefore, we won’t pay much attention to it at all in the rest of the post.

Towards capability

Functions in VLMonad will look, as functions in a monad do, like

somefunction :: A -> VLMonad Ops B

Where Ops represent the possible effects. For instance, if you need a state effect, you would define Ops as

data Ops m = Ops
{ put :: Int -> m ()
, get :: m Int
}

But, after all, VLMonad is simply a newtype: we could very well inline its definition.

somefunction :: A -> forall m. Monad m => Ops m -> m B

The ordering of types and argument is not too idiomatic, though. So let’s rearrange them a little:

somefunction :: Monad m => Ops m -> A -> m B

If this may look like a familiar style of structuring effect, it is. See for instance the style advertised by Éric Torreborre in a recent blog post. It’s not really so much an alternative to free monads as a different presentation of free monads (albeit slightly more liberal, if, as per previous section, we ignore the restriction from O’Connor’s comment).

Personally, I find it rather tiresome to explicitly carry around the capabilities (the Ops thing) at every function call. I’d rather keep my function arguments for the program logic, and leave all the plumbing to the monad. Therefore, I turn Ops into a type class, and move it “left of the fat arrow”: really in Haskell A -> B and A => B mean the same thing, they only differ in whose responsibility it is to pass the arguments around.

somefunction :: (Monad m, Ops m) => A -> m B

The definition of Ops for a state effect would, then, become

class Ops m where
put :: Int -> m ()
get :: m Int

This is precisely the style of programming supported by the capability library (see also our announcement blog post).

Closing thoughts

Free monads, capabilities-as-records and capabilities-as-type-classes, are, essentially, three different flavours of the same thing (with free monads technically being the more restrictive of the three, as it can’t have exception handling effects).

Choosing between the three is, ultimately, a matter of taste. I really like capabilities-as-type-classes because it pushes the boilerplate outside of the domain logic.

At the end of the day, what really matters is the core idea shared by these three approaches: capabilities should be expressed in terms of the domain logic, not in terms of implementation details.

Arnaud Spiwack

Arnaud is Tweag's head of R&D. He described himself as a multi-classed Software Engineer/Constructive Mathematician. He can regularly be seen in the Paris office, but he doesn't live in Paris as he much prefers the calm and fresh air of his suburban town.