This vignette introduces the functional programming concept of
*monad*, without going into much technical detail.
`{chronicler}`

is an implementation of a logger monad, but in
truth, it is not necessary to know what monads are to use this package.
However, if you are curious, read on. A monad is a computation device
that offers two things:

- the possibility to decorate functions so they can provide additional output without having to touch the function’s core implementation;
- a way to compose these decorated functions;

(This definition is an oversimplification of the actual definition of a monad, but good enough for our purposes.)

To understand what a monad is, I believe it is useful to explain what sort of problem monads solve.

Suppose for instance that you wish for your functions to provide a log when they’re run. If your function looks like this:

```
<- function(x){
my_sqrt
sqrt(x)
}
```

Then you would need to rewrite this function like this:

```
<- function(x, log = ""){
my_sqrt
list(sqrt(x),
c(log,
paste0("Running sqrt with input ", x)))
}
```

There are two problems with such an implementation:

- we need to rewrite every function we need to use so that they provide logs;
- these functions don’t compose.

What do I mean with “these functions don’t compose”? Consider another
such function `my_log()`

:

```
<- function(x, log = ""){
my_log
list(log(x),
c(log,
paste0("Running log with input ", x)))
}
```

`sqrt()`

and `log()`

compose, or rather, they
can be chained:

```
10 |>
sqrt() |>
log()
#> [1] 1.151293
```

while this is not true for `my_sqrt()`

and
`my_log()`

:

```
10 |>
my_sqrt() |>
my_log()
```

`Error in log(x) (from #3) : non-numeric argument to mathematical function`

This is because `my_log()`

expects a number, not a list
which is what `my_sqrt()`

returns.

A “monad” is what we need to solve these two problems. The first problem, not having to rewrite every function, can be tackled using function factories. Let’s write one for our problem:

```
<- function(.f, ..., log = NULL){
log_it
<- deparse(substitute(.f))
fstring
function(..., .log = log){
list(result = .f(...),
log = c(.log,
paste0("Running ", fstring, " with argument ", ...)))
} }
```

We can now create our functions easily:

```
<- log_it(sqrt)
l_sqrt
l_sqrt(10)
#> $result
#> [1] 3.162278
#>
#> $log
#> [1] "Running sqrt with argument 10"
<- log_it(log)
l_log
l_log(10)
#> $result
#> [1] 2.302585
#>
#> $log
#> [1] "Running log with argument 10"
```

We can call `l_sqrt()`

and `l_log()`

*decorated* functions and the values they return *monadic*
values.

The second issue remains though; `l_sqrt()`

and
`l_log()`

can’t be composed/chained. To solve this issue, we
need another function, called `bind()`

:

```
<- function(.l, .f, ...){
bind
.f(.l$result, ..., .log = .l$log)
}
```

Using `bind()`

, it is now possible to compose
`l_sqrt()`

and `l_log()`

:

```
10 |>
l_sqrt() |>
bind(l_log)
#> $result
#> [1] 1.151293
#>
#> $log
#> [1] "Running sqrt with argument 10"
#> [2] "Running log with argument 3.16227766016838"
```

`bind()`

takes care of providing the right arguments to
the underlying function. We can check that the result is correct by
comparing it the `$result`

value from the returned object to
`log(sqrt(10))`

:

```
log(sqrt(10))
#> [1] 1.151293
```

This solution of using a function factory and defining a helper
function to make the decorated functions compose is what constitutes a
monad, but strictly speaking, this is not precisely correct. It can be
interesting to see the actual definition from the programming language
Haskell, which is a pure functional programming language where monads
*must* be used to solve certain issues:

`Monad`

class. All the common monads are members of it:```
class Monad m where
(>>=) :: m a -> ( a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
```

(Source: Monad)

This definition is quite cryptic, especially if you don’t know
Haskell, but what this means is that a `Monad`

(in Haskell)
is *something* that has three methods:

`>>=`

which is what we called`bind()`

;`>>`

which I didn’t bother implementing, because it’s not really needed for understanding what a monad is;- and
`return`

. Don’t be confused by the name, this has nothing to do with the`return()`

we use inside functions to return a value.`return`

is a function that wraps (or converts) a value into a monadic value, so if you consider any object`a`

,`return`

takes`a`

as an input and*returns*the monadic value`m a`

.

While we didn’t implement `return`

(also called
`unit`

, which is also not a good name), our function factory
`log_it()`

does `return`

/`unit`

’s job
but it *returns* `m f(a)`

instead of `m a`

.
Using function factories comes more naturally to R users than using
`return`

/`unit`

, hence why I did not focus on
`return`

/`unit`

. Also, using our function factory,
it is easy to implement `return/unit`

:

`<- log_it(identity) unit `

so `return/unit`

is just the `identity()`

function that went through the function factory. In a sense, the
function factory is even more necessary for defining a monad than
`return/unit`

.

Finally, you might read sometimes that monads are objects that have a
`flatmap()`

method. I think that this definition as well is
not strictly correct and very likely an oversimplification. But what is
`flatmap()`

anyways? In practical terms, it is equivalent to
`bind()`

, but it is how you get there that’s different. To
implement `flatmap()`

two additional functions are needed:
`fmap()`

and `flatten()`

(which is quite often
called `join()`

, but this has nothing to do with
*joining* data frames, so I used `flatten()`

instead).

`fmap()`

is a function that takes a monadic value as an
argument and an undecorated function and applies this undecorated
function to the monadic value:

```
<- function(m, f, ...){
fmap
<- deparse(substitute(f))
fstring
list(result = f(m$result, ...),
log = c(m$log,
paste0("fmapping ", fstring, " with arguments ", paste0(m$result, ..., collapse = ","))))
}
```

Let’s first define a monadic value:

```
# Let’s use unit(), which we defined above, for this.
<- unit(10))
(m #> $result
#> [1] 10
#>
#> $log
#> [1] "Running identity with argument 10"
```

Let’s now use `fmap()`

to apply a non-decorated function
to `m`

:

```
fmap(m, log)
#> $result
#> [1] 2.302585
#>
#> $log
#> [1] "Running identity with argument 10" "fmapping log with arguments 10"
```

Great, now what about `flatten()`

(or
`join()`

)? Why is that useful? Suppose that instead of
`log()`

we used `l_log()`

with `fmap()`

(so we’re using a decorated function instead of an undecorated one):

```
fmap(m, l_log)
#> $result
#> $result$result
#> [1] 2.302585
#>
#> $result$log
#> [1] "Running log with argument 10"
#>
#>
#> $log
#> [1] "Running identity with argument 10" "fmapping l_log with arguments 10"
```

As you can see from the output, this produced a nested list, a
monadic value where the value is itself a monadic value. We would like
`flatten()/join()`

to take care of this for us. So this could
be an implementation of `flatten()`

:

```
<- function(m){
flatten
list(result = m$result$result,
log = c(m$log))
}
```

Let’s try now:

```
flatten(fmap(m, l_log))
#> $result
#> [1] 2.302585
#>
#> $log
#> [1] "Running identity with argument 10" "fmapping l_log with arguments 10"
```

Great! Now, as explained earlier, `flatmap()`

and
`bind()`

are the same thing. But we have implemented
`flatten()`

and `fmap()`

, so how do these two
functions relate to `flatmap()`

? It turns out that
`flatmap()`

is the composition of `flatten()`

and
`fmap()`

:

```
# I first define a composition operator for functions
`%.%` <- \(f,g)(function(...)(f(g(...))))
# I now compose flatten() and fmap()
# flatten %.% fmap is read as "flatten after fmap"
<- flatten %.% fmap flatmap
```

So this means that we can now replace:

```
10 |>
l_sqrt() |>
bind(l_log)
#> $result
#> [1] 1.151293
#>
#> $log
#> [1] "Running sqrt with argument 10"
#> [2] "Running log with argument 3.16227766016838"
```

by:

```
10 |>
l_sqrt() |>
flatmap(l_log)
#> $result
#> [1] 1.151293
#>
#> $log
#> [1] "Running sqrt with argument 10"
#> [2] "fmapping l_log with arguments 3.16227766016838"
```

and we get the same result (well, not quite, since the log is
different). I prefer introducing monads using `bind()`

,
because `bind()`

comes as a natural solution to the problem
of decorated functions not composing. Not so with
`flatmap()`

, but in some applications it might be easier to
first define `flatten()`

and `join()`

and get
`flatmap()`

instead of trying to write `bind()`

directly, so it’s good to know both approaches.

Before continuing with the final part of this introduction, I just
want to share with you that lists are also monads. We have everything we
need: `as.list()`

is `unit()`

,
`purrr::map()`

is `fmap()`

and
`purrr::flatten()`

is `flatten()`

. This means we
can obtain `flatmap()`

from composing
`purrr::flatten()`

and `purrr::map()`

:

```
# Since I'm using `{purrr}`, might as well use purrr::compose() instead of my own implementation
<- purrr::compose(purrr::flatten, purrr::map)
flatmap_list
# Functions that return lists: they don't compose!
# no worries, we implemented `flatmap_list()`
<- \(x)(as.list(sqrt(x)))
list_sqrt <- \(x)(as.list(log(x)))
list_log
10 |>
list_sqrt() |>
flatmap_list(list_log)
#> [[1]]
#> [1] 1.151293
```

(thanks to @armcn_ for showing me this)

In sum, monads are useful when you need values to also carry
something more with them. This *something* can be a log, as shown
here, but there are many examples. For another example of a monad
implemented as an R package, see the maybe monad.
`{chronicle}`

actually takes advantage of the
`{maybe}`

package and uses the maybe monad to handle cases
where functions fail. I provide a short introduction to the maybe monad
in the Maybe
monad vignette.

Monads need to satisfy the so-called “monadic laws”. We’re going to
verify if the monad implemented in `{chronicler}`

satisfies
these monadic laws.

The first law states that passing a monadic value to a monadic
function using `bind()`

(or in the case of the
`{chronicler}`

package `bind_record()`

) or passing
a value to a monadic function is the same.

```
<- as_chronicle(10)
a <- record(sqrt)
r_sqrt
test_that("first monadic law", {
expect_equal(bind_record(a, r_sqrt)$value, r_sqrt(10)$value)
})#> Test passed 🎊
```

Turns out that this is not quite the case here; the logs of the two objects will be slightly different. So I only check the value.

The second law states that binding a monadic value to
`return()`

(called `as_chronicle()`

in this
package, in other words, the function that coerces values to chronicler
objects) does nothing. Here again we have an issue with the log, that’s
why I focus on the value:

```
test_that("second monadic law", {
expect_equal(bind_record(a, as_chronicle)$value, a$value)
})#> Test passed 😸
```

The third law is about associativity; applying monadic functions successively or composing them first gives the same result.

```
<- as_chronicle(10)
a
<- record(sqrt)
r_sqrt <- record(exp)
r_exp <- record(mean)
r_mean
test_that("third monadic law", {
expect_equal(
(bind_record(a, r_sqrt)) |>
(bind_record(r_exp)
$value,
)
(|>
a bind_record(x, r_sqrt) |> bind_record(r_exp))()
(\(x) $value
)
)
})#> Test passed 😸
```

`chronicle`

objectsFor exhaustivity’s sake, I check that I can get
`flatmap_record()`

by composing `flatten_record()`

and `fmap_record()`

:

```
<- record(sqrt)
r_sqrt <- record(exp)
r_exp <- record(mean)
r_mean
<- 1:10 |>
a r_sqrt() |>
bind_record(r_exp) |>
bind_record(r_mean)
<- purrr::compose(flatten_record, fmap_record)
flatmap_record
<- 1:10 |>
b r_sqrt() |>
flatmap_record(r_exp) |>
flatmap_record(r_mean)
identical(a$value, b$value)
#> [1] TRUE
```