Functional programming has become popular these days, but unlike object-oriented languages, each FP language is so different from the other. Some of these use strict evaluation while others use lazily evaluated models; tons of new concurrent models were introduced; further more, states are handled differently too.
Haskell, for example, does not have states, but uses its powerful type system to construct the stateful program flow normally used in other languages. As you might guess, Monad is one of the type that does the trick. Defining a Monad type is pretty much like defining a class in an object oriented language. However, Monad can do much more than a class. It’s a type that can be used for exception handling, constructing parallel program workflow or even a parser generator!
By learning Monad, You’ll know a different perspective of how to program, and rethink the composition of data logic beyond the object-oriented programming kingdom.
What is a type
Before we jump into Monad itself, let’s quickly go through the type systems. What is a type? We usually think of type as a static thing like int
or float
, but actually, type is more dynamic; types are the rules associated with the data, not the actual data itself. It is important to remember this point.
For example, an int
is treated as int
only when you use addl
CPU instruction to present +
operation on a 64 bit machine. On the other hand, float
uses addss
the instruction for +
and %xmm
registers for computations. It’s the rules, or the generated instructions, that define the type.
The whole concept of Object-Oriented Programming is to let you use classes/interfaces to define types, the rules (methods) that interacts with the actual data. For more advanced needs people use templates(c++) or generics(java) to define more abstracted rules that are more reusable. Monad, is pretty much like that generic class.
Now we understand what a type is; it’s just a set of rules, or methods in Object-Oriented terms. A Monad, is just yet another type, and the definition of this type is defined by four rules:
- bind
(>>=)
- then
(>>)
return
fail
By going through these four rules and looking at the examples below, you’ll understand what a Monad is and why is it so awesome and powerful!
Monad lesson 1: the bind operator (>>=)
The Monad bind operator is a function type signature. A type signature is like an abstract method defined in an abstract class:
1 2 |
|
You may not familiar with Haskell syntax, but actually it’s pretty straight forward.
It takes 2 inputs and returns an output.
-
A Monad that contains type
a
as the first input -
A function
(a -> m b)
as the second input.(a -> m b)
is a first order function that takesa
as input, and returns Monadm b
. You can think of this as a delegate design pattern in Object-Oriented world, except you don’t need to pass in a whole object, but only a function itself is sufficient. -
The implementation will return Monad
m b
as a result.
Why is this definition powerful? Well, one benefit is that you can wrap common logic in Monad and save yourself time.
Application 1: exception handling
Exception handling is always a pain in the ass. Lets demonstrate how to use a Monad to reduce the code size and un-nest if-else blocks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
There’s nothing wrong with nested if-else blocks. The logic is straightforward and easy to understand. To un-nest the if-else statements, there are several ways to do it. You can either use a goto
statement and pass the control to a barrier label, or use try-catch block in C++ or a similar language that supports it. Or, here comes the Maybe Monad for the rescue!
1 2 3 4 5 |
|
A Maybe Monad has two constructors: Nothing
and Just a
. It’s a simple kind of error Monad, where all errors are represented by Nothing
. The rules are simple:
- If the first input
M a
isJust x
, run the second input (the function) with valuex
- If the first input is
Nothing
, just returnNothing
When you combine several Maybe Monad handling functions together, if one of the upstream function went wrong by returning Nothing
, all the downstream function(s) won’t be executed.
1 2 3 4 |
|
The first line is Haskell function type definition. maybeHalf
takes an Int
and returns Maybe Int
type. Other than that, the function body tells us:
-
If input
a
is even, returna / 2
wrapped inMaybe
Monad constructorJust
. -
Otherwise, return
Maybe
Monad constructorNothing
.
1 2 3 4 5 6 |
|
Cool, now the nested if-else logic is replaced! What do we learn from this?
-
Error handling if-else logic is encapsulated from the user.
-
User can use the defined data type
Just a
orNothing
to lift the information (correct or error) to upper Monad.
A programming language’s type system deeply colors the way we think and write code in that language. In the object-oriented kingdom, we use objects to encapsulate error messages, and then code the program to throw the exception upwards and hopefully the try-catch block will capture it and handle the error gracefully. In contrast, you can use a type to build the try-catch block, and also encapsulate error or success state!
Application 2: accumulate states
So far, we’ve seen that Monad can help us wrap common (usually nested) logic into a type.
User can lift the data to the upward Monad, and Monad will do the if-else logic for you.
Nevertheless, Monad can do more for you! Maybe
Monad only passes the data to the next
user defined function, and here we introduce the Writer
Monad, which will accumulate the
data you lift to it.
Writer Monad, type definition
Here is the type definition of Writer Monad:
1 2 3 |
|
Don’t be scared! Writer Monad uses Monoid
to concat the data you lift to it.
Monoid
defines mempty
and mappend
functions. mappend
, as it’s name suggests, appends
the data you passed to it; and mempty
is just an empty element.
mappend
and mempty
are just abstract ways to merge data together.
1 2 3 4 5 6 |
|
The Writer Monad’s definition simply takes the return value of user defined function f
, extracts
the value v'
out of it, and uses mappend
to append the old v
and new v'
.
Writer Monad, in action
Let me take a modified example from Learn You a Haskell for Great good. To log the process of deriving the greatest common divisor, we can do this:
1 2 3 4 5 6 7 8 9 10 |
|
The Writer Monad does not have a constructor like Maybe Monad does, so you need to use tell
and return
to build the Writer Monad and return it back. To show the result of the writer log, you’ll also need function runWriter
.
1 2 3 4 5 6 7 |
|
Monad Lesson 2: the then operator >>
So, now we learned you can put different logic into a Monad to operate states. All the state passing is done by the bind operator >>=
, which wraps the data and passes it to the downstream handler. However, Sometimes, we don’t care about the wrapped value and just want to pass the state downstream. For example, performing side effects.
The definition of then operator >>
looks like this:
1 2 3 |
|
Unlike bind operator >>=
which unwraps the value passed between user defined functions, then operator ignores the wrapped value (it uses _
as variable) and only captures the states x
and y
.
What is the use case of this? Recall that Haskell is a purely a functional language that doesn’t have states or variables; However, the Monad system can encapsulate hidden states, so the solution is to put a hidden #realworld#
variable in IO Monad
that records the standard input-output status, and also the file seek position, etc.
Application 3: IO Monad
It’s easier to understand in examples. Let’s try the putStrLn
function, which takes a String
, and returns an IO Monad
.
1
|
|
Since it doesn’t need to take things from standard input, we have no need to bind variables. The usage is trivial:
1 2 3 4 5 |
|
With every >>
operator, the IO ()
Monad changes the #realworld#
variable in the background. It’s just like Writer
Monad changes the accumulated states, but more abstract. For more details about the IO ()
Monad, please visit IO inside - HaskellWiki.
do notation
For convenience, Haskell provides us do notation to simplify the writing of bind and then, so instead of writing:
1 2 3 4 5 |
|
you can write:
1 2 3 4 5 |
|
This example is taken from IO inside - HaskellWiki. All the Monads above can be rewritten in this form. Hooray!
Monad Lesson 3: return
and fail
We only have two functions left to describe Monad, which are return
and fail
. Actually, we already know them! The complete Maybe
definition is:
1 2 3 4 5 6 7 8 9 |
|
The return
function is the wrapper that we have used so far, and fail
is the function to represent, as you can guess, failure. The definition of return
and fail
in Monad is:
1 2 3 |
|
fail
can take an additional string to report the failure message. With bind, then, return, and fali functions, we then know the whole definition of the Monad type!
Monad Lesson 4: Beyond states
So far, we introduced Maybe
, Writer
, and IO ()
Monads which use states heavily, but Monad can do more than abstract states. Remember, the core of Monad type is to wrap around logic. One of the applications it can do is define work flow. Here comes the Eval Monad to the rescue; let’s take a look on the example in the online book:
1 2 3 4 |
|
Once you wrap the function f
in Eval
Monad, it generates the logic to evaluate the code in parallel. You can further use the combination of the rpar
and rseq
functions defined by Eval
, to construct the dependencies of each individual job. Check the link for more details!
Another nontrivial example of Monad is Parsec parser combinator library. In this case, the wrapped logic is used for generating a parser from smaller components. Read Write Yourself a Scheme in 48 hours to explore this decent Monad!
Conclusion
I started to learn Haskell a while ago, but I found explaining what a Monad type is in a easy way was extremely difficult. The final approach I choose was to break it down into four functions and explore them through examples. I found this helped me understand it better, and I hope it helps you out as well!
Once you understand what the Monad type is, you can easily use for all sorts of Haskell libraries on the fly. In the Haskell world, Monad is THE design pattern that is used for almost every important library. Functional programming introduced a different way to convolute the logic and data. It’s also elegant and powerful. I hope you enjoyed reading this post!
Any comments are welcome. ;)