## The beauty and the horror of Haskell monads

They are wonderful, but terrible

This post assumes some understanding of and familiarity with Haskell monads.

### The beauty

There are a number of patterns that come up frequently in programming. Some of these are:

- Combining IO actions, making the result of each available to later ones:
*IO monad* - Building up, or reading from, a peusdo-mutable state or configuration:
*Reader, Writer, and State monads* - Running a sequence of functions where the later are skipped if the previous doesn't return a meaningful result:
*Maybe monad* - Running a function over all possible combined values of a number of lists:
*List monad*

The beautiful thing is that problems that at face value are completely different actually share a common structure. They can be decomposed into their domain-specific components, and `>>=`

(bind) and `return`

, where, although different for all of the above examples, adhere to certain laws. Once you are familiar with any given instance of `>>=`

and `return`

, then it's really quite amazing how much can be achieved, and communicated, with just a few lines of code.

### The horror

The "once you are familiar with any given instance of `>>=`

" hints at the horror. The `>>=`

and `return`

functions are (terribly?) overloaded. Each of the monads use the same notation, but they can all do radically different things. To know what any `>>=`

actually does, you have to infer its type from the surrounding code, and look to its definition. Similarly a do-block's behaviour depends radially on the definition of `>>=`

: and it might be even worse, because `>>=`

isn't even present in the code.

Taking and modifying some examples from What I Wish I Knew When Learning Haskell, we can see a use of the Maybe monad,

```
main :: IO ()
main = putStrLn . show $ example
example :: Maybe Int
example = do
a <- Just 3
b <- Nothing
return $ a + b
```

and an example of the Reader monad.

```
import Control.Monad.Reader
data MyContext = MyContext
{ foo :: String
, bar :: Int
} deriving (Show)
main :: IO ()
main = putStrLn . show $ runReader computation $ MyContext "hello" 1
computation :: Reader MyContext (Maybe String)
computation = do
n <- asks bar
x <- asks foo
if n > 0
then return (Just x)
else return Nothing
```

The monadic values `example`

and `compution`

definitions have similarities: do notation, <-, and return. However, these similarities are misleading. Take for example `return`

from the Maybe monad. Its definition is

```
return = Just
```

while the Reader monad's definition is

```
return a = Reader $ \_ -> a
```

Similarly, because of the different implementations of `>>=`

, line breaks in the do-notations result in different code. Hence how monads are sometimes described as *programmable semicolons*.

There is a *lot* of non-explicit behaviour going on. In code reviews, I often use the term "magical" if there is non-explicit behaviour, and I usually suggest it is to be avoided. Yes, `>>=`

and `return`

are likely to abide by the monad laws, and they certainly do in these examples, but I'm unsure if this is enough to understand what's going on.

As a parallel, Mike Acton, in his talk on data-oriented design, suggests only using C++ operator overloading that is "super obvious", such as adding vectors. Uncle Bob also teaches us to be more explicit.

The name of a variable, function, or class, should answer all the big questions. It should tell you why it exists, what it does, and how it is used.

Robert C. Martin

There are reasonable arguments that monads in Haskell, by overloading `>>=`

and `return`

, break these guidlines. Of course, perhaps I will feel differently once I have been working with Haskell for longer. We will see!