Fantas, Eel, and Specification 15: Monad

Today is the day, Fantasists. We all knew it was coming, but we hoped it wouldn’t be so soon. Sure enough, though, here we are. We’ve battled through weeks of structures, and reached the dreaded Monad. Say your goodbyes to your loved ones, and let’s go.

Ahem.

A Monad is a type that is both a Chain and an Applicative. That’s… well, it, really. We’re done here. Next time, we’ll be looking at the new-wave wizardry of Extend. Until then, take care!


… Right, so maybe we could say a little more, but only if we want to! Honestly, though, the above is enough to get going. We’ve seen a few examples of Semigroup-and-Monoid-feeling relationships, but let’s focus on two in particular:

In the Apply post, we said that ap felt a bit Semigroup-ish. Then, in the Applicative post, we saw that adding of gave us something Monoid-ish.

Later, we looked at Chain, where chain gave us something like a Semigroup. So, you’re asking, where’s the Monoid? Well, with a Monad, of doubles up as the empty to Chain’s Semigroup, too!

We’re going to go through a pretty mathematical definition of Monad first, so don’t be discouraged if it doesn’t make sense on the first few reads. This is really just background knowledge for the curious; skip ahead if you just want to see a practical example!


To skip ahead, start scrolling!

Let’s do some mind-blowing; it’s the Monad post after all, right? Let’s first define two composition functions, compose and mcompose:

//- Regular `compose` - old news!
//+ compose ::      (b -> c)
//+         -> (a -> b)
//+         ->  a   ->    c
const compose = f => g => x =>
  f(g(x))

//- `chain`-sequencing `compose`, fancily
//- known as Kleisli composition - it's the
//- K in Ramda's "composeK"!
//+ mcompose :: Chain m
//+          =>        (b -> m c)
//+          -> (a -> m b)
//+          ->  a     ->    m c
const mcompose = f => g => x =>
  g(x).chain(f)

I’ve tried to line up the types so it’s a bit clearer to see left-to-right how this works… I hope that it helped in some way!

compose says, “Do g, then f”. mcompose says the same thing, but does it with some kind of context (little language extension bubble, remember?). That m could be Maybe in the case of two functions that may fail, or Array in the case of two functions that return multiple values, and so on. What’s important is that, to use mcompose, our m must be a Chain type.

Now, you can make something very monoid-looking with regular compose:

const Compose = daggy.tagged('f')

//- Remember, for semigroups:
//- concat :: Semigroup s => s -> s -> s
//- Replace s with (a -> a)...
//+ concat ::      (a -> a)
//+        -> (a -> a)
//+        ->  a   ->    a
Compose.prototype.concat =
  function (that) {
    return Compose(
      x => this(that(x))
    )
  }

//- We need something that has no effect...
//- The `id` function!
//+ empty :: (a -> a)
Compose.empty = () => Compose(x => x)

Mind blown yet? Function composition is a monoid! The x => x function is our empty (because it doesn’t do anything), and composition is concat (because it combines two functions into a pipeline). See? Everything is just monoids. Monoids all the way down.

Typically, the Compose type is used for other things (remember the Traversable post?), but we’re using it here as just a nice, clear name for this example.

Now, here’s the real wizardry: can we do the same thing with mcompose? Well, we could certainly write a Semigroup:

const MCompose = daggy.tagged('f')

//- Just as we did with Compose...
//+ concat :: Chain m
//+        =>        (a -> m a)
//+        -> (a -> m a)
//+        ->  a     ->    m a
MCompose.prototype.concat =
  function (that) {
    return MCompose(
      x => that(x).chain(this)
    )
  }

concat now just does mcompose instead of compose, as we expected. If we want an empty, though, it would need to be an a -> m a function. Well, reader mine, it just so happens that we’ve already seen that very function: from Applicative, the of function!

//- So, we need empty :: (a -> m a)
//+ empty :: Chain m, Applicative m
//+       => (a -> m a)
MCompose.empty = () =>
  MCompose(x => M.of(x))

// Or just `MCompose(M.of)`!

Note that, as with lots of interesting Monoid types, we’d need a TypeRep to build MCompose to know which M type we’re using. This is written out in THE CODE GIST FIXME

To make MCompose a full Monoid, we need our M type to have an of method and be Chainable. Chain for the Semigroup, plus Applicative for the Monoid.

Take a breath, Fantasists: I’m aware that I might be alone here, but I think this is beautiful. No matter how clever we think we’re being, it’s all really just Semigroups and Monoids at the end of the day. Under the surface, it never gets more complex than that.

Let’s not get too excited just yet, though; remember that there are laws with empty. Think back to the Monoid post: it has to satisfy left and right identity.

// For any monoid x...
x
  // Right identity
  === x.concat(M.empty())

  // Left identity
  === M.empty().concat(x)


// So, for `MCompose` and some `f`...
MCompose(f)

  // Right identity
  === MCompose(f).concat(MCompose.empty())

  // Left identity
  === MCompose.empty().concat(MCompose(f))

//- In other words, `of` can't disrupt the
//- sequence held inside `mcompose`! For
//- the sake of clarity, this just means:

f.chain(M.of).chain(g) === f.chain(g)
f.chain(g).chain(M.of) === f.chain(g)

And there we have it: of cannot disrupt the sequence. All it can do is put a value into an empty context, placing it somewhere in our sequence. No tricks, no magic, no side-effects.

So, for your most strict and correct definition, M is a Monad if you can substitute it into our MCompose without breaking the Monoid laws. That’s it!


For those skipping ahead, stop scrolling!

Ok, big deal, Monad is to Chain as Monoid is to Semigroup; why is everyone getting so excited about this, though? Well, remember how we said we could use Chain to define execution order?

//+ getUserByName :: String -> Promise User
const getUserByName = name =>
  new Promise(res => /* Some AJAX */)

//+ getFriends :: User -> Promise [User]
const getFriends = user =>
  new Promise(res => /* Some more AJAX */)

// e.g. returns [every, person, ever]
getUser('Baymax').chain(getFriends)

With this, we can define entire programs using map and chain! We can do this because we can sequence our actions. What we get with of is the ability to lift variables into that context whenever we like!

const optimisedGetFriends = user
  user.name == "Howard Moon"
  ? Promise.of([]) // Lift into Promise
  : getFriends(user) // Promise-returner

We know that getFriends returns a Promise, so our speedy result needs to do the same. Luckily, we can just lift our speedy result into a pure Promise, and we’re good to go!

Although it may seem improbable, we actually now have the capability to write any IO logic we might want to write:

const Promise = require('fantasy-promises')

const rl =
  require('readline').createInterface({
    input: process.stdin,
    output: process.stdout
  })

//+ prompt :: Promise String
const prompt = new Promise(
  res => question('>', res))

//- We use "Unit" to mean "undefined".
//+ speak :: String -> Promise Unit
const speak = string => new Promise(
  res => res(console.log(string)))

//- Our entire asynchronous app!
//+ MyApp :: Promise String
const MyApp =
  // Get the name...
  speak('What is your name?')
  .chain(_ => prompt)
  .chain(name =>

    // Get the age...
    speak('And what is your age?')
    .chain(_ => prompt)
    .chain(age =>

      // Do the logic...
      age > 30

      ? speak('Seriously, ' + name + '?!')
        .chain(_ => speak(
          'You don\'t look a day over '
            + (age - 10) + '!'))

      : speak('Hmm, I can believe that!'))

    // Return the name!
    .chain(_ => Promise.of(name)))

//- Our one little impurity:

// We run our program with a final
// handler for when we're all done!
MyApp.fork(name => {
  // Do some database stuff...
  // Do some beeping and booping...

  console.log('FLATTERED ' + name)
  rl.close() // Or whatever
})

That, beautiful Fantasists, is (basically) an entirely purely-functional app. Let’s talk about a few cool things here.

Firstly, every step is chained together, so we’re explicitly giving the order in which stuff should happen.

Secondly, we can nest chain to get access to previous values in later actions.

Thirdly, chain means we can do everything with arrow functions. Every command is a single-expression function; it’s super neat! Try re-formatting this example on a bigger screen; all my examples are written for mobile, but this example can look far more readable with 80-character width!

Fourthly, following on from the first point, there’s no mention of async with chain - we specify the order, and Promise.chain does the promise-wiring for us! At this point, async behaviour is literally just an implementation detail.

Fifthly (are these still words?), MyApp - our whole program - is a value! It has a type Promise String, and we can use that String! What does that mean? We can chain programs together!

//+ BigApp :: Promise Unit
const BigApp =
  speak('PLAYER ONE')
  .chain(_ => MyApp)
  .chain(player1 =>

    speak('PLAYER TWO')
    .chain(_ => MyApp)
    .chain(player2 =>

      speak(player1 + ' vs ' + player2)))

OMGWTF! We took our entire program and used it as a valuetwice! As a consequence, we can just write lots of little programs and chain (compose, concat, bind, whatever you want to say) them together into bigger ones! Remember, too, that Monads are all also Applicatives

//+ BigApp_ :: Promise Unit
const BigApp_ =
  lift2(x => y => x + ' vs ' + y,
    speak('PLAYER ONE').chain(_ => MyApp),
    speak('PLAYER TWO').chain(_ => MyApp))

Oh yeah! Our programs are now totally composable Applicatives, just like any other value. Our entire programs! All a functional program really does is collect some little programs together with ap and chain. It really is that neat!

Why do we use fantasy-promises instead of the built-in Promise? Our functional Promise doesn’t execute until we call fork - that means we can delay the call until we’ve defined its behaviour. With a built-in Promise, things start happening immediately, which can lead to non-determinism and race conditions. This way, we maintain full control!

Of course, maybe the syntax is a bit ugly, but that’s what helper functions are for! Also, why stop at Promise? This fanciness works for Maybe, Array, Either, Function, Pair, and so many more!

Keep fiddling, using those Task/Promise isomorphisms to do things in parallel, using Maybe to avoid undefined / null along the way, using Array to return multiple choices; if you can handle all that, you’re a fully-fledged functional aficionado!


You might be wondering what the rest is for if we now have all the tools we’ll ever need, and that’s certainly a good question. The rest are optional; monadic functional programming doesn’t require an understanding of Comonad or Profunctor, but nor does it require an understanding of Alt or Traversable; these are just design patterns to help our code to be as polymorphic as possible.

As always, there’s a Gist for the article, so have a play with it! Here’s a little idea for an exercise: write a monadic functional CLI app to play “higher or lower”. You know everything you need to know; trust me!