Monads in Functional Programming: a Practical Note
Before we begin — yes, this is yet another article about monads in Haskell.
Now, I know you might be thinking there are already a plethora of articles trying to demystify what actually a monad is in functional programming. Unfortunately it somehow remains a mystery; a seemingly magical concept that only those who have honed their FP craftsmanship can understand. It sometimes can be heard in exchanges between two functional wizards, followed with nods of understanding as if that explains everything.
But is it really that hard of a concept?
Being a learner of the functional path of programming myself, it seemed to be a rather interesting subject to be explored. And, of course, I picked the most (in)famous functional language to learn it from: Haskell.
This article assumes the readers to have written basic Haskell programs before. We won’t get into the syntax basics, using GHCI, or compiling and running Haskell programs. Some might argue that Haskell syntax is intuitive, so if you don’t know Haskell but up for a challenge, then please continue!
In using Haskell, you will inevitably encounter monads. Want to print something? Use IO monad. Handling nulls? There’s that Maybe monad for you right there. But what exactly are they?
It never really clicked, until I read an article by @jrsinclair. I even wrote a tweet about it (okay, okay, this is a shameless attempt of gaining new followers):
As you may have noticed, the funny thing about it is that the article is not using Haskell, but JavaScript. But that delivers an important point: that monads are not exclusive to Haskell. It exists in general functional paradigm. And JavaScript is an amazing functional language!
(By the way, I really like @jrsinclair’s way of writing, I recommend you to read that article… after you read this, of course.)
So yeah, I like to think that I am one step closer to understanding what monads really are, and this blog post will serve as a note for my future self in the likely case that I will forget. I will take an approach that I think is interesting for explaining it: what monads are based on the problems they solve. I hope this piece will be able to serve as a gentle introduction for monads in functional programming, specifically when using Haskell.
Be aware that I am still a beginner and I have no background on category theory, so there will be technical mistakes here and there. If you find one, please let me know in the comments!
(For the experienced: in this article I deliberately left out explanations about Functors and Applicatives in order to focus more on Monads from a practical perspective. I hope I don’t deviate too much from the theory!)
Motivation: Pure and Simple
The core of functional paradigm in programming is pure functions. Your function gets an input, and it returns back an output based only on that input. They don’t have side effects; they don’t communicate with the world outside of their input and returned values. Pure functions are so awesome: they are easily testable, maintainable, yadda yadda. There’s an interesting article that explores this characteristic in more depth.
Ideally, we work with “simple” values: Ints, Strings, Bools, Employees, etc. And by simple, I mean just that: they’re just there, a concrete value in our program. In Haskell, you can achieve it, for example, by assignments:
a :: Int
a = 10 + 5b :: Employee
b = Employee 1234 “Bobby”
However, in the real world, the computations that produce the value we want may not be “simple”. They might have quirks, such as:
- It may not produce result in some conditions, for example getting a nonexistent property of a JSON object.
- The number of produced result may not be deterministic (none, a few, many).
- While computing the result, it may interact with the outside world, for example printing to console or making network requests.
- It may eventually produce result.
- The result may depend on some form of state, and the computation may alter that state.
- It may produce errors.
It’s not an exhaustive list, but hopefully it lets you see a common pattern. Those quirks are all nitty gritty details. What if they don’t exist? What if we could treat them as “simple” values?
As a software developer, we always strive to use abstractions to help us simplify the problem at hand, and monads will help us in this case.
How monads can help
A monad acts as a container that abstracts away those quirks in the computations, and let us focus more on what we want to do with the contained values.
Let’s take one more look at the previous quirks and see what specific monad abstracts away those problems.
- Result may or may not exist: solved by the Maybe monad.
- Nondeterministic number of result: solved by the List monad.
- Outside world interaction: solved by the IO monad.
- Eventual result: solved by the Promise/Future monad.
- Dependence on state: solved by the State monad.
- Errors: solved by the Error monad.
There are many more monads that solves other specific problems (eg. Reader and Writer monad, see here for references), but they all serve the same purpose of abstraction. As a container, they specify what kind of value they have and what characteristics the computation that produce the value might have.
Let’s get into some examples. Suppose you write an asynchronous code in JavaScript with Promise:
function square(x) {
return x * x
}function getNumber() {
// suppose this is an asynchronous operation, like
// fetching row counts from a database.
return Promise.resolve(10)
}getNumber()
.then(square)
.then(squared => {
/* do something with the value */
})
In the above example, square
is a pure function. It acts on a single value, x
, and returns the square of x
. The function getNumber
, however, will eventually return a result. But we don’t want to care about that; all we want to know is that we want to square the value of the number. Promise is a monad that abstracts away the asynchronous part of the computation. If you want to know more about Promise as a monad, you might want to read this article.
Another example, if we write a program in Haskell that prints a list of squares:
square :: Int -> Int
square x = x * xmain :: IO ()
main = print result
where
numbers = [1, 2, 3, 4]
result = map square numbers
The value numbers
is a list of integers. The fact that it’s a list says that it might have zero or more elements. What we want to achieve is a list of squares of the elements in numbers
. We have a function square
takes a “simple” integer and squares it. What if we can use that function to achieve the list we want, without caring how many elements there actually are? It turns out that we can, using the builtin map
function of Haskell.
One thing that you might need to notice from the above examples is the functions .then()
and map
, which lets us operate with the values inside the container. I like to call them monad operations, and we’ll get to know more about them in the next section.
Monad operations
Okay, so we established a monad as a container. Then what? It’s no use if we can’t do anything with the contained value. It appears that there are more to monads than just containers: they also define operations that can be done to manipulate the value that they contain, and we’re going to look at them now.
For the purpose of explanation, we’ll refer to values contained in a monad container as monadic value.
1. return
Problem: If we have a “simple” value, how can we “wrap” it inside a monad to make it a monadic value?
It may not seem intuitive at the moment as to why we might want to do this, but the answer will serve an important role for the other operations; sometimes we want to introduce a simple value within computations with monadic values.
The answer is: return
. In Haskell, return
does not behave as you would expect in imperative languages such as Java, C, or JavaScript, which indicate a returned value of a function or method. Instead, it wraps the value inside a monad of the context. Here is an example:
simpleTwice :: String -> String
simpleTwice word =
word ++ " " ++ wordmonadicTwice :: String -> IO String
monadicTwice word =
return $ simpleTwice wordmain :: IO ()
main =
let
valueFromSimple = simpleTwice "Hello"
in do
valueFromMonadic <- monadicTwice "Hello"
print valueFromSimple
print valueFromMonadic
In the above example, simpleTwice
repeats a word twice, separated by a space. monadicTwice
wraps the result of simpleTwice word
into a monadic value of type IO String
. In main
, we can see how both values can be used. The <-
operator “extracts” the value of the monadic IO value; we can do that since main
operates in IO monad.
If you remember the previous JavaScript Promise example, Promise.resolve(10)
acts as the monad return
in that it wraps the simple value 10
inside a Promise.
2. fmap
Problem: If we have a monadic value of type M T (Maybe T, IO T, etc.), and we have a function that takes a simple value of type T, how can we apply the function into the contained value?
More concretely, suppose we have a function that takes a string and return the same string, uppercased. We then have a monadic value of type, say, Maybe String or List String. How can we apply the function to the monadic value? That is, we just want to transform the contained value with our function without having to pull it out of the container.
We also need the application to respect the quirks, such that for Maybe, it should return Nothing
if the value does not exist, and for List, we want to apply it for each element, even if the list is empty.
Thankfully, fmap
to the rescue! Let’s take a look at another example.
import Data.Charupper :: String -> String
upper = map toUppermaybeValue :: Maybe String
maybeValue = return "Hello"main :: IO ()
main =
print alsoMaybeValue
where
alsoMaybeValue = fmap upper maybeValue
That piece of code will print Just “HELLO”
; we apply the pure function upper
to the monadic Maybe value Just “Hello”
. fmap
does the unwrapping and rewrapping for us, so we don’t have to. Neat!
Notice that we use return
there. We could write it as Just "Hello"
, and the program will behave the same. It’s because in Maybe, return = Just
. If you also remember, the map
function that we used before is actually fmap
, only for lists.
3. join
Problem: If we have a nested monadic value of the same monad type, can we simplify it by removing the nesting?
Take a look at the monadicTwice
function from the 1. return section above. It takes a simple String and return a monadic value of type IO String. Now, suppose we already have a monadic value of type IO String, and we want to apply monadicTwice
to it. We can, using fmap
! However, fmap
doesn’t care about the return value, and it always wraps it back with the monad. If we run it, we get the following sequence:
fmap
unwraps the IO String into String- the String then get used as the argument to
monadicTwice
, which returns IO String fmap
wraps the IO String back with IO monad, so we end up with the nested IO (IO String)!
So how do we solve this? If we have such nested monadic value, how can we “flatten” the nesting?
There’s join
for that. If we run GHCI, we can prove it:
Prelude> import Control.Monad -- brings in join
Prelude Control.Monad> let a = Just (Just "Hello")
Prelude Control.Monad> a
Just (Just "Hello")
Prelude Control.Monad> join a
Just "Hello"
Turns out that the combination of fmap
and join
is so common that we arrive at the next operation…
4. bind (or chain)
Problem: If we have a monadic value of type M T (Maybe T, IO T, etc.), and we have a function that takes a simple value of type T and returns a monadic value of type M T, how can we apply the function into the contained value such that the end result does not nest the monad?
This problem statement is essentially the same with section 2. fmap, but with the addition of the emphasized parts. We know that to solve the nesting we have join
. Combining fmap
and join
, we get what we call bind
(or, in some literatures, chain
). In Haskell, the corresponding operator is notated >>=
. Yes, that weird symbol that often appears in people’s Haskell code, we’ve got a name for it now! Let’s see this in GHCI:
Prelude> let makeMaybe x = Just x
Prelude> let maybeDouble x = Just (x + x)
Prelude> fmap maybeDouble (makeMaybe 10)
Just (Just 20)
Prelude> makeMaybe 10 >>= maybeDouble
Just 20
I hope the example is clear enough. If we apply maybeDouble
to makeMaybe 10
with fmap
, we get Just (Just 20)
, which often is not what we want. Instead, we use >>=
to pass the result of makeMaybe 10
into maybeDouble
. We then get the desired result, Just 20
. If you’re not convinced, let’s try it with fmap
and join
:
Prelude> import Control.Monad
Prelude Control.Monad> join $ fmap maybeDouble (makeMaybe 10)
Just 20
Bind is so commonly used in Haskell programs that it’ll give you so much insight in understanding them if you know what they do. In fact, the .then()
function of JavaScript Promise is an example of bind
, in the sense that it can transform Promises into simple values for subsequent computations and chaining.
Note: there’s also =<<
which is essentially the flipped version of >>=
. You can test it with maybeDouble =<< makeMaybe 10
and it will also return Just 20
.
5. liftM
Problem: If we have a function that accepts “simple” values as its arguments, can we somehow “upgrade” it to accept monadic values?
We already have a function that adds two numbers. We now have two monadic values Maybe 20
and Maybe 30
. Can we somehow add these two maybes using the previous function?
Yes, we can! By using the liftM
function, specifically liftM2
. You can think of “liftMn” as “upgrading” a function of arity “n” to accept monadic values, so liftM2
works with function with arity 2, liftM3
with arity 3, and so on. The Control.Monad
package only gives us liftM
untuk liftM5
(but if you have more than 5 arguments for a function, you should probably refactor anyway). Let’s check it out.
import Control.MonadmonadicValueA = Just 20
monadicValueB = Just 30add :: Int -> Int -> Int
add x y = x + ymonadicAdd :: Maybe Int -> Maybe Int -> Maybe Int
monadicAdd = liftM2 addmain :: IO ()
main = print $ monadicAdd monadicValueA monadicValueB
If you run the program, it will print Just 50
as the result of adding Just 20
and Just 30
. Nice!
6. >=> (monadic compose)
Problem: If we have a function F1 that accepts a simple value of type A and produces a monadic value of type M B, and we have a function F2 that accepts a simple value of type B and produces a monadic value of type M C, can we somehow compose those functions to achieve a function which accepts a simple value of type A and produces a monadic value of type M C?
That’s one long problem statement.
Basically we want to compose those functions. To understand, try to substitute the types above: A = Int, M = Maybe, B = String, and C = Bool. Those substitution is kind of arbitrary; you can make it anything you want, really, as long as M are a monad type.
Let’s see another example.
import Control.Monadf1 :: Int -> Maybe String
f1 x
| x > 10 = Just "I have a hat!"
| otherwise = NothingequalsA :: Char -> Bool
equalsA c = c == 'a'f2 :: String -> Maybe Bool
f2 str =
Just $ any equalsA strf3 :: Int -> Maybe Bool
f3 = f1 >=> f2main :: IO ()
main =
print $ f3 20
Again, this example is just arbitrary, I tried to stay true to the types we determined above. Here, f1
takes an Int and return Maybe String, while f2
takes a String and return Maybe Bool. We then define f3
as the composition of f1
and f2
. If we run it, we get Just True
! Just what we are aiming for.
If you’re not following, f3 20
will first pass 20
to f1
, returning Just "I have a hat!"
. It then will pass the string "I have a hat"
to f2
, which checks if it contains a character 'a'
. It then will return Just True
. If we change 20
to, say, 5
, f1
will return Nothing
, and f3
will also return Nothing
! That’s nice, the composition took care of handling Nothing
value for us when passing to f2
.
(Like bind
, there’s also the flipped version of >=>
, which is <=<
. The flipped version acts more like the normal function composition with .
. You can try it by changing the definition of f3
into f2 <=< f1
and it will work.)
With this, we have reach the end of our exploration with monad operations.
Keep in mind that these operations have different implementation details for different monad types, but you don’t really need to know them. In Haskell, Monad is a type class. Type classes defines basic operations and functions that need to be implemented for instances of them. Maybe, IO, etc. are instances of the Monad type class, hence they have those operations. If it helps, you can think of type classes as interfaces in Java.
There are many sources of explanations regarding Monad as a type class (and also its formal definition), I recommend you to look them up!
Recap!
In this article, we established the role of monads as containers for abstracting away the “quirks” of a computation, enabling us to focus only on the “simple” values. We also explored the operations available for manipulating the contained, “monadic” values inside it.
I hope this piece can give you another view on understanding monads from a practical perspective. Keep in mind that we don’t touch any definition here; there are many more literatures and articles that explains it, and I don’t know if I could do it better. I don’t want to ruin your theoretical understanding.
If you notice significant mistakes and misunderstandings on my part, do tell me in the comments.
That’s it, then! Let’s keep the fun in functional programming!