Ray Shih

@rayshih771012

Functional Computational Thinking — What is a monad?

If you are using functional programming, whether using a real functional language or any language that supports first-class function, you may have already used monads at some point. Most people just didn’t know they are monads. In this article, I am going to explain what is monad without using any mathematical theorem. Even more, I will demonstrate how to derive monad by only using plain JavaScript.

WARNING: This is an advanced topic. May not suit for beginner.
DISCLAIMER: This article won’t provide the exact definition of monad. So don’t read it if you have an exam about monad.

I have been learning Haskell for about half year. I like to watch conference/meetup videos from youtube. One day, I watched a video called “Let’s be mainstream!”, in which the ELM creator, Evan Czaplicki, said “monad is callback”. I thought I understood what is monad that time, but I couldn’t figure out why Evan said that. Here is the link to that video, you can check after you finish this article if you want.

Several weeks passed, after reading lots of learning materials, especially after watched “Why Do Monads Matter” by Derek Wright. In this video, he demonstrate several different monads by trying to solve one problem: function composition.

The follow explanation basically use the same concept, but I’m going use JavaScript instead of Haskell.

So first, as a functional programmer, we all know function composition, which is very very easy, like this:

The composition function composeF is very easy, it takes two arguments f and g, and return a new function which return the result value after applying g then f. And we can use this function to combine mul3 and add1 to form the new function addOneThenMul3.

Now, assume we have a scenario:

What we have:

  1. Two files, first file (file1) contains the path pointing to second file (file2), and second file contains the real content we want.
  2. A read file function

Next, we need to construct the function to fetch the “real content”.

The readFileSync is a synchronous IO function, which takes a path string as a input and return content of that given path in string. So we can reuse composeF to compose the function we want: readFileContentSync.

So far so good!

But what if the readFile function is asynchronous? If you are familiar with Node.js, then you probably have used callback to do flow control, which has a formal name: continuation-passing style or CPS.

Then the readFile function will look like this:

Now, here is a problem: We cannot use composeF to do function composition. Because this time, the function doesn’t return anything.

But wait, we can still do function composition as long as:

  1. The derived function takes the same input as the first function, and
  2. It does what those functions do in sequence.

All we need is a new composition function:

Notice that: I changed the arguments’ sequence: from (f, g) to (g, f).

You can see, composeCPS will return a new function which will call g and in the callback of g, call f, and call cb with the the result value of f in the end.

Hooray! we can do function composition again!

Ok, let’s try something interesting.

First, we adjust the function signature of readFileCPS into readFileHOF:

const readFileHOF = path => cb => {
readFileCPS(path, cb)
}

HOF stands for Higher order Function. We just break one set of arguments into two set of arguments. Which makes readFileHOF takes a path argument and returns a function which take cb as input, and the real function body will execute after all arguments set.

And then, we need a new composition function.

Which is almost the same with composeCPS, not so different.

Secondly, we give a name to the function which take cb as argument, and put it into an object, like this:

Also, because we change the function signature again, so we need to adjust the composition function:

Which is actually, again, not so different from composeHOF, right?

We hate Repeat Yourself. So let’s create a helper function:

const createExecObj = exec => ({exec})

This function returns an object with exec field, which is also a function. Let’s call the “object with exec field” ExecObj

createExecObj also make readFileEXEC two line shorter:

const readFileEXEC2 = path => {
return createExecObj(cb => {
readFileCPS(path, cb)
})
}

After this change, you can see readFileEXE2 takes a path and return an exec object. Other than that, everything is the same.

Next step, we are going to have a big change, so BE AWARE!

So far, the both arguments of composition function are functions. Let’s change the first argument to something else: the exec object!

We rename the compose to bind. This bind function will return a new exec object. If anyone call exec of this object, then it will do the same thing to execObj and then in the callback, it uses f. Notice that line 4 of this gist is the same as the line 6 of composeEXEC.

Then the magic start:

const readFile2EXEC2 = bindExec(
readFileEXEC2('./file1'),
readFileEXEC2
)
readFile2EXEC2.exec(result => console.log(result))

Not clear enough? Let’s remove the constant:

bindExec(
readFileEXEC2('./file1'),
readFileEXEC2
)
.exec(result => console.log(result))

Still don’t get it? Well, we’re almost there, be patient:

Let’s try to “inline” the bindExec function to the exec object, like this:

Please compare this with bindExec. The return object is the exec object, so we simply replace the execObj with this. Of course, because of the same reason, we don’t need first argument (line 3)

So how to use it?

readFileEXEC2('./file1')
.bind(readFileEXEC2)
.exec(result => console.log(result))

Do you see that? Something very familiar? No?

Ok! Let’s do some rename:

  • readFileEXEC2 -> readFileAsync
  • bind -> then
  • exec -> done
readFileAsync('./file1')
.then(readFileAsync)
.done(result => console.log(result))

Oh my god! This is a promise!!!
(Not exactly. Because promise will execute whether you call done.)

Or

  • readFileEXEC2 -> readFile$
  • bind -> flatMap
  • exec -> subscribe

Oh my god! This is Rx!!!
(Yes! almost! Of course we still need to add error handling and other stuff)

So where is monad?

The answer is: from composeCPS until here. They are all monads!!! Specifically:

  • The readFileCPS becomes a monad because of composeCPS. Actually, it is called Cont monad in Haskell
  • The “exec object” becomes a monad because of bind. Also, it is called IO monad in Haskell, check File IO in Haskell

So how to identify whether a thing is a monad?

Well, first, maybe you’re asking a wrong question. Instead of asking “Is it a monad”, you should ask “Can it be a monad”.

What properties does a monad have?

  1. It has a context with a value.
  2. The value doesn’t have to exists.
  3. It provides a way to get the value (usually by callback)
  4. A bind function which able to pull the value out from the the monad (first argument) and put that value into another function f as argument. (f should be a function that return a monad). And the returned value of bind function should be the same type of the first argument.
  5. The bind function often also named flatMap

So monad is for async operation? NO!!!

For example:

Check with those properties:

  1. Yes! Square bracket [] is context.
  2. It’s ok to exists.
  3. By forEach.
  4. Because map will return nested array (line 14), so map is not conform 4. We need to create a flatMap function.
  5. As 4.

So yes, array can be a monad.

Is it necessary to understand what is monad in order to work?

No!

For example, lots of developers using array, promise and Rx without understand monad. It’s OK. So did I. I used Rx for a long time without really understand monad. But still, it is helpful if you want to create a tool like Rx. (Well, we just did it :p)

Conclusion

So “Monad is Callback“?

Yes! According to property 3.

Then “Callback is Monad”?

Not yet! Only if there is a bind function defined.

Thanks to Tom Chen, Guan-Yu Pan, Wu, Ching Ting to for reviewing this article.

UPDATE: fix wrong wording: passion => patient, thanks to Ian Hofmann-Hicks
UPDATE: rename readFileFluent, thanks to wkliang

Topics of interest

More Related Stories