2019-11-26

The first time I heard about Monad was at a Scala Meetup. Later, I tried to understand the concept of Monad but was overwhelmed by various voluminous books and tutorials on Haskell. Then I came across Ruan Yifeng’s article “Illustrated Monad“ published in 2015. Although it was clear and easy to understand, it was detached from Haskell, and the illustrations didn’t match the concepts in the language. Ruan Yifeng’s article was translated from “Functors, Applicatives, And Monads In Pictures,” which I read through.

Computer programs are used to control a computer to perform calculations. The objects operated on by programs are various types of values, such as numerical values. Here’s a simple value `2`

:

Using a function to process the value can return the result of the function execution, such as:

Apart from simple numerical types, values can also be contained within some contextual environment, forming more complex value types. You can think of the contextual environment as a box, with the numerical value placed inside the box. This box as a whole is described as `Just 2`

, which is a boxed `2`

:

If you are familiar with Java, you can think of this box as a wrapper class, like Integer and int, corresponding to boxed and unboxed 2.

Facing a boxed `2`

, we can’t directly apply the `+3`

function to it:

At this point, we need a function `fmap`

to operate on it. `fmap`

will first extract the value 2 from `Just 2`

, then add 3 to it, put the result 5 back into the box, and return `Just 5`

:

How does `fmap`

know how to parse `Just`

? What if it was another type like `Only`

, could it still parse it? That’s why Functor is needed to complete the definition of this operation.

A Functor is a type of data type:

Functor defines the behavior of `fmap`

:

`fmap`

has two input parameters and one output parameter. The input parameters are a function and a boxed value, and the output parameter is a boxed value. It can be used like this:

```
fmap (+3) (Just 2)
-- Just 5
```

Returning to Haskell, the Haskell “system library” has an instance of `Functor`

called `Maybe`

, which defines the behavior of `fmap`

, specifying how to operate on values when the input parameter is of type `Just`

:

```
instance Functor Maybe where
fmap func (Just val) = Just (func val)
fmap func Nothing = Nothing
```

The entire process of the expression `fmap (+3) (Just 2)`

is similar to this:

Similarly, from the definition of `Maybe`

, it can be seen that if the second parameter passed to `fmap`

is `Nothing`

, the function will return `Nothing`

. This is indeed the case:

```
fmap (+3) Nothing
-- Nothing
```

Now suppose a scenario in Java where a user uses a utility class Request to make a request to a server, and the request returns a type Response. Response is an entity class that may or may not contain the required data:

```
Response res = Request.get(url);
if (res.get("data") != null) {
return res.data;
} else {
return null;
}
```

In Haskell, using `fmap`

would become:

```
fmap (get("data")) (Response res)
```

Of course, Haskell doesn’t have a `get("data")`

syntax. You can encapsulate the operation of getting `Response.data`

as a function `getData`

, then pass it into `fmap`

as the first parameter.

Haskell provides a syntax sugar `<$>`

for `fmap`

to simplify its usage:

```
getData <$> (Response res)
```

Next, think about how Haskell functions operate on lists. The function performs calculations on each element of the list and then returns a list:

In fact, lists are also Functors. Here is the definition of lists:

```
instance Functor [] where
fmap = map
```

`Applicatives`

is another concept. We previously said that values are placed in boxes. What if functions are also placed in boxes?

Haskell provides the operator `<*>`

to handle functions inside boxes:

For example:

```
Just (+3) <*> Just 2 == Just 5
```

Using `<*>`

can also accomplish some interesting operations, such as multiplying and adding each element in a list:

```
[(*2), (+3)] <*> [1, 2, 3]
-- [2, 4, 6, 4, 5, 6]
```

Function execution involves processing values using functions with arguments, involving three roles. `Functors`

place the processed value in a box, `Applicatives`

place the function in a box, and `Monads`

place the function’s argument in a box. Monads have an operator `>>=`

to achieve Monad functionality. Suppose there is a function `half`

that takes a numerical value as an argument. If the value is even, it divides by 2; otherwise, it returns Nothing:

```
half x = if even x
then Just (x `div` 2)
else Nothing
```

How do you pass a `Just`

type value to `half`

?

`>>=`

can solve this problem:

```
Just 3 >>= half
-- Nothing
```

The `>>=`

operator takes `Just 3`

, transforms it into `3`

, and processes it in `half`

. A `Monad`

is a data type that defines the behavior of `>>=`

:

```
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
```

Here, `Maybe`

is a `Monad`

(existing alongside the `Maybe`

mentioned above):

```
instance Monad Maybe where
Nothing >>= func = Nothing
Just val >>= func = func val
```

`>>=`

also supports chain operations:

```
Just 20 >>= half >>= half >>= half
-- Nothing
```

Although Haskell’s Monad is quite famous, it actually involves three concepts: `Functors`

, `Applicatives`

, and `Monads`

. Perhaps Monad has more extensive applications. In data processing, FP is not superior to OOP; the logic is similar, only the writing style differs. Facing the same problem with different thinking and expressions corresponds to different programming ideas and paradigms. There are many intricate theories in the world waiting for us to explore.