Skip to content

Commit

Permalink
Explain Reader.
Browse files Browse the repository at this point in the history
  • Loading branch information
athas committed Sep 7, 2024
1 parent ae9cdbb commit d144f6f
Showing 1 changed file with 162 additions and 6 deletions.
168 changes: 162 additions & 6 deletions src/chapter_2.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,28 +280,184 @@ do x <- foo one_argument
...
```

## The Reader Monad
## Examples of defining and using monads

In the following we will look at examples of how to define and use our
own monads. Most of these correspond to monads that are available and
commonly used in standard Haskell libraries.

### The Reader Monad

The Reader monad is a commonly used pattern for implicitly passing an
extra argument to functions. It is often used to maintain
configuration data or similar context that would be annoyingly verbose
to handle manually, perhaps because it is not used in all cases of a
function. We will call this implicit value an *environment*.

Operationally, a Reader monad with environment `env` and producing a
value of type `a` is represented as a function accepting an `env` and
returning `a`.

```Haskell
{{#include ../haskell/readerstate.hs:Reader}}
```

Coming up with a definition of the Reader type itself requires actual
creativity. On the other hand, the `Functor`/`Applicative`/`Monad`
instances can almost be derived mechanically. I recommend starting
with the the following template code:

```Haskell
instance Functor (Reader env) where
fmap = liftM

instance Applicative (Reader env) where
(<*>) = ap
pure x = undefined

instance Monad (Reader env) where
m >>= f = undefined
```

The `fmap` and `(<*>)` definitions are done, as discussed above. All
we have to do is fill out the definitions of `pure` and `>>=`. I
usually start with `pure`, because it is simpler. We start with this:

```Haskell
pure x = undefined
```

The `Applicative` class requires that the `pure` instance for `Reader
env` has type `a -> Reader env a`. This means we know two things:

1. `x :: a`.
2. The right-hand side (currently `undefined`) must have type `Reader
env a`.

Looking at the type definition of `Reader` above, we see that *any*
value of type `Reader` takes the form of a `Reader` constructor
followed by a payload. So we write this:

```Haskell
pure x = Reader undefined
```

Looking at the definition of `Reader` again, we see that this
`undefined` must have type `env -> a`.

~~~admonish hint
Instead of using `undefined`, we can also use `_`. This is a so-called
*hole*, and will cause the compiler to emit an error message
containing the type of the expression that is supposed to be located at the hole.
~~~

How do we construct a value of type `env -> a`? Well, we have a
variable of type `a` (namely `x`), so we can simply write an anonymous
function that ignores its argument (of type `env`) and returns `x`:

```Haskell
{{#include ../haskell/readerstate.hs:Monad_Reader}}
pure x = Reader $ \_env -> x
```

This concludes the definition of `pure`. We now turn our attention to
`>>=`. The line of thinking is the same, where we systematically
consider the types values we have available to us, and the types of
values we are supposed to construct. This is our starting point:

```Haskell
{{#include ../haskell/readerstate.hs:Functor_Reader}}
{{#include ../haskell/readerstate.hs:Applicative_Reader}}
m >>= f = undefined
```

## The State Monad
We know:

1. `m :: Reader env a`
2. `f :: a -> Reader env b`
3. `undefined :: Reader env b`

We don't have anything of type `a`, so we cannot apply the function
`f`. One we can do is deconstruct the value `m`, since we know this is
a single-constructor datatype:

```Haskell
Reader x >>= f = undefined
```

Now we have the following information:


1. `x :: env -> a`
2. `f :: a -> Reader env b`
3. `undefined :: Reader env b`

The values `x` and `f` are functions for which we do not have values
of the required argument type, so we cannot do anything to. But we can
still start adding a constructor to the right-hand side, just as we
did above for `pure`:

```Haskell
Reader x >>= f = Reader undefined
```

And again, we know that the `undefined` here must be a function taking
an `env` as argument:

```Haskell
Reader x >>= f = Reader $ \env -> undefined
```

So far we have not used any creativity. We have simply done the only
things possible given the structure of the types we have available.
We now have this:

1. `x :: env -> a`
2. `f :: a -> Reader env b`
3. `env :: env`
3. `undefined :: b`

Since we now have a variable of type `env`, we can apply the function
`x`. We do so:

```Haskell
Reader x >>= f = Reader $ \env ->
let x' = x env
in undefined
```

Now we have `x' :: a`, which allows us to apply the function `f`:

```Haskell
Reader x >>= f = Reader $ \env ->
let x' = x env
let f' = f env
in undefined
```

We have `f' :: Reader env b`, so we can pattern match on `f'` to
extract the payload. When a type has only a single constructor, we can
do this directly in `let`, without using `case`:

```Haskell
Reader x >>= f = Reader $ \env ->
let x' = x env
let Reader f' = f env
in undefined
```

Now we have `f' :: b`, which is exactly what we need to finish the
definition:

```Haskell
Reader x >>= f = Reader $ \env ->
let x' = x env
let Reader f' = f env
in f'
```

This finishes the `Monad` instance for `Reader`.

### The State Monad

```Haskell
{{#include ../haskell/readerstate.hs:State}}
Expand All @@ -316,7 +472,7 @@ function. We will call this implicit value an *environment*.
{{#include ../haskell/readerstate.hs:Applicative_State}}
```

## Combining Reader and State
### Combining Reader and State

One limitation of monads - in particular the simple form we study in
AP - is that they do not compose well. We cannot in general take two
Expand Down

0 comments on commit d144f6f

Please sign in to comment.