Functors For Nothing, Monads For Free
To the delight of functional programmers and the umbrage of everyone else, Monads have crept their way into Real World™ programming.
Being a perspicacious programmer you’ve noticed that from Promises
in JavaScript ES6 to the Maybe
-esque null-coalescing operator introduced in C# 6, the once feared M-word is not only extant but here to stay.
So, having ham-fisted a monad or two in a project and taken a cursory look at the mtl
library you’ve got a chip on your shoulder. Once again you’re ahead of the curve, you fear no interview questions! You can now say in a droll voice and with an air of authority at dinner parties “of course, a monad is simply a monoid in the category of endofunctors”. The opposite sex swoons.
But there’ve been whispers, rumours. There’s a new kid on the block: Free Monads
. Tide, time and computer science stop for no man, the unusually far-sighted 12th century adage goes.
There’s a number of good blog posts out there but personally, I find them prolix and unclear in their connection to traditional monads for the uninitiated. Allow me to give a cursory overview of implementing a free monad from scratch to applying it - though in keeping it simple and bridging understanding we’ll have to gloss over the algebraic syntax tree properties that make free monads special for now.
First, what is a free monad? In short, a free monad is a way of taking any data structure that forms a functor, and getting a monad for no extra effort. Nothing. Free. Through just defining a functor instance you’ve now got do-notation and a monad transformer!
The title of this post alludes to the Maybe
data type; for the purposes of illustrating exactly how we can create a monad from just a functor, I’m instead going to use an isomorphism of Maybe
. Where Maybe
is defined:
We will define a new F-Algebra, though a facsimile. Thus allowing us to start with nothing predefined for us:
Now we have a data structure that models two states, augmenting a pure value with whether there was success, and with it the valid value, or failure which will instead have an absence of a value.
As we know, a data structure alone does not a monad, applicative or functor make. So we need a functor instance for this:
Et voilà! Let’s define a convenient type synonym, equipping StatusF
with Free
from Control.Monad.Free
:
type Status a = Free StatusF a
And we’ve got ourselves a free monad. We can use do-notation to model computations with a context of failure - and we did it all ourselves.
Let’s define a couple of helper functions to save typing
We’ve taken the pure constructors we defined in the data type and composed them with liftF
from Control.Monad.Free
- taking the results into the realm of free monad.
So let’s see it in action. With nothing defined other than a functor instance, no behind the curtains trickery, we’ve got a monad complete with do notation. For a contrived example, let’s show a safe version of the head
function that won’t blow up when passed an empty list:
safeHead :: [a] -> Status a
safeHead xs = do
x <- myTail xs
return x
where myTail (x:xs) = success x
myTail [] = failure
With FoldFree id
and providing a traditional monad implementation for StatusF
we can prove the two implementations isomorphic. I leave this, of course, as an exercise to the reader.
A single layer monad however is limited in application. What if we wanted to extend State
, Reader
or, the forbidden fruit, IO
with our newfangled monad?
Fortunately for us, the Free
monad is hiding more than it lets on: it’s already using transformer, composed with Identity
as its base. Let’s ditch Control.Monad.Free
and instead use the following imports:
Our previously defined Status
monad is constrained to having Identity
as its base, so we’ll have to define a new type that will let us choose an underlying monad to add our effect to:
Those helper functions we defined earlier, we simply change the types and we’ve got functions that can lift into our transformer!
And so we enter contrived example number two: workplace password security is at an all time low. If having a password length greater than five isn’t going to stop hackers, nothing will says management. Ok so we’ve got a requirement that can pass or fail, let’s write a function for it:
validation :: Monad m => String -> StatusT m String
validation password
| (length password) > 5 = success password
| otherwise = failure
We’ve now got a function that will work for any monad passed into it. As we’re getting user input we’re interested in IO
:
Notice the lift
from the Control.Monad.Trans
package, it’s not a typo. We need to lift from a regular monad to a free transformer so liftF
is no good for us here.
So far so good. But how do we get results out of it? At the start I hand waved away the structure of Free
- but now we’ve got to face it. Free
’s strength comes from building up a structure as works its magic. The lisp veterans among you will know how powerful this is. However, as we’re trying to imitate the forgetful nature of a monad we’re going to have to interpret this tree.
Free
can be thought of as a tree structure: the eponymous Free
constructor creates its branches, and Pure
is its leaves.
So, with our vague understanding we know we’re going to have to check our two Free
elements and the Pure
leaf
runStatusIO :: StatusT IO String -> IO ()
runStatusIO f = do
x <- runFreeT f
case x of
Pure r -> print r
Free (Success x) -> runStatusIO x
Free (Failure) -> print "Not good enough!"
So what’re we doing here? It takes a free monad transformer as an input and runs runFreeT
on it and binds it to x. runFreeT
is a function that evaluates the first layer of structure, in essence it steps through it one at a time.
After evaluating the output we’re going to want to do one of three things:
If it’s a
Pure
value we know we’re at the bottom of the structure and can stop recursing here and print the value.A
Success
means we’re headed in the right direction, we need to dig deeper to get to our value. The structure thus far is put right back into the function again.Failure
is a short circuit, we can stop here and admonish users as neccessary.
And we’ve done it! Free
is nothing to fear. To be clear, the above doesn’t do anything you couldn’t do with Monad
. The real power of Free
I plan to show at a later date, working with the built up structure we can accomplish different ways of interpreting an algebraic syntax tree - whether skipping branches or evaluating it under different contexts.