Monads in Functional Programming: a Practical Note

Bobby Priambodo
13 min readDec 1, 2016

--

A random horses image to get you reading :) [source]

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 + 5
b :: 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:

  1. It may not produce result in some conditions, for example getting a nonexistent property of a JSON object.
  2. The number of produced result may not be deterministic (none, a few, many).
  3. While computing the result, it may interact with the outside world, for example printing to console or making network requests.
  4. It may eventually produce result.
  5. The result may depend on some form of state, and the computation may alter that state.
  6. 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.

  1. Result may or may not exist: solved by the Maybe monad.
  2. Nondeterministic number of result: solved by the List monad.
  3. Outside world interaction: solved by the IO monad.
  4. Eventual result: solved by the Promise/Future monad.
  5. Dependence on state: solved by the State monad.
  6. 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 * x
main :: 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 ++ " " ++ word
monadicTwice :: String -> IO String
monadicTwice word =
return $ simpleTwice word
main :: 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 toUpper
maybeValue :: 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:

  1. fmap unwraps the IO String into String
  2. the String then get used as the argument to monadicTwice, which returns IO String
  3. 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 30
add :: Int -> Int -> Int
add x y = x + y
monadicAdd :: Maybe Int -> Maybe Int -> Maybe Int
monadicAdd = liftM2 add
main :: 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 = Nothing
equalsA :: Char -> Bool
equalsA c = c == 'a'
f2 :: String -> Maybe Bool
f2 str =
Just $ any equalsA str
f3 :: Int -> Maybe Bool
f3 = f1 >=> f2
main :: 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!

--

--

Bobby Priambodo

Software Engineer. Functional programming and distributed systems enthusiast. Java, JavaScript, Elixir, OCaml, Haskell.