I’ve been learning Haskell for a few months now and I think I have a good understanding of what monads are. I’m not an expert so I should warn you this answer might be completely wrong.
Monadic bind is the name given to the (>>=) function or bind function, also known as chain, flatMap, or joinMap.
Consider the following Haskell program which takes a number from stdin and prints its square root in stdout.
If you read the (>>=) as then. The program basically read as:
main = getLine then stringToNumber then squareRoot *then *print.
In Practice, most people use the “Do Notation” which is just syntactic sugar over the monadic bind syntax. Once, you understand this, do notation should be quite easy.
For reference, here’s the same program converted to “Do notation”.
To know what this function actually does, we first need to know what a monad is. The definition of monad can be found on Wikipedia Page on Monads:
a **monad* is a design pattern that allows structuring programs generically while automating away boilerplate code needed by the program logic.*
Right now, this sentence doesn’t make sense, hopefully, it will make some sense after I give a few examples.
Example 1: Handling Null values (Maybe type).
Let’s say we have a value which may or may not exist (nullable), we want to perform some computations on the value. These computations could also fail and return null values. Our code will be:
This code violates the DRY Principle and is quite ugly because of all the nesting. Let’s try to abstract out the repetitive code.
We can design a maybe data type which holds either some value or Null. And we can define a function that does null checking for you.
Now, instead of our functions returning null, it will return this Maybe data type. Here’s an example implementation with some functions returning Maybe type.
Then we can write our program into a flat pipeline instead of the nested hell we had earlier.
If at any point the function returns Null, all other computations will be skipped. (Like if we divide by zero or square root a negative number).
Now, look again at the definition of the monad I gave above, and see if it makes a little bit more sense now:
a **monad **is a design pattern that allows structuring programs generically while automating away boilerplate code needed by the program logic.
Example 2: Handling Async Values (Promises)
Let’s say that instead of returning Null values, our functions returned *Async *values, meaning that values that don’t exist yet but will be available in future. (For example, it’s an API request or Database call).
Here, instead of checking for null values, we need to *wait *for the value of the previous result to be available because we execute any function.
We have seen that the then function in Maybe type performs an “if check” for us between every function execution. The then *function in Promises waits for the previous result and executes the given function when it is available.
This allows us to convert a nested callback code into a flat pipeline of functions.
Hopefully, you can see a pattern here and somewhat grasp the abstract concept of monads.
But what about the IO Monad in Haskell?
The IO Monad works on the same principle. As we have seen before, monadic bind performs some intermediate computation in between functions, removing the need for us to write boilerplate code.
In this case, the boilerplate code would be pulling out the value obtained by performing side effect and passing it to the next function in our chain of functions.
They’re also quite similar to promises meaning that they guarantee the order of execution by creating a dependency on previous value for the function to execute. Example,
main = function1 >>= function2 >>= function3
Here, function1 depends *on the value returned by function2. It cannot run until function1 is finished. Similarly, function3* **cannot run until function2 has finished.
If you want to actually see what bind function in IO monad does, watch this video. It is the best explanation I’ve found for IO monad.
** sidenote: Promise is not actually monad, but it’s pretty close and solves the same problem that monads solve. My purpose here is to build intuition and not be 100% accurate.*