11 Jan

Monads

Read “You Could Have Invented Monads! (And Maybe You Already Have)” from the exercises sheet and follow along.

This time, no new examples will be provided, only additional explanations and tips for solving the exercises in the sheet.

Audit

Function that also logs something, aside from returning its value.

1 bind

You can think of bind f as an “upgraded” f, and just write what it does with its argument: (prevRes, prevLog).

Some examples of logging functions:

add5 :: Float -> (Float, String)
add5 x = (x + 5, "add5 was called")

divideBy2 :: Float -> Audit Float  -- same as (Float, String)
divideBy2 x = (x / 2, "divideBy2 was called")
> add5 10
(15, "add5 was called")

Composing them using the bind function:

divideThenAdd :: Float -> (Float, String)
divideThenAdd = (bind add5) . divideBy2
> divideThenAdd 20
15  -- 20/2=10 +5=15

Using the composition operator #:

divideThenAdd :: Float -> (Float, String)
divideThenAdd = add5 # divideBy2

2 unit

Remember that lift f is a function, so it may be easier to understand if it is written like this:

lift :: (a -> b) -> (a -> Audit b)
(lift f) x = unit (f x)

Or even:

(lift f) x = (f(x), "")

Which explains what the lifted f does to its argument, f: it creates a pair of the normal result f(x) and logs nothing ("").

Nondet

Function that returns multiple values of the same type – like a list.

4 bind

Again, remember to treat bind f as a function. It may also be useful to remember what the built-in function concat does.

Some example non-deterministic functions:

oppositeToo :: Int -> [Int]
oppositeToo x = [x, -x]

plus1n2n3 :: Int -> Nondet Int  -- same as [Int]
plus1n2n3 x = [x + 1, x + 2, x + 3]
> oppositeToo 5
[5, -5]

> plus1n2n3 10
[11, 12, 13]

Composing them:

plusOpposites :: Int -> [Int]
plusOpposites = oppositeToo # plus1n2n3
-- or explicitly with the `bind` function: (bind oppositeToo) . plus1n2n3
> plusOpposites 10
[11,-11, 12,-12, 13,-13]

5 unit

Just like before, keep in mind that lift f should be treated like a function:

lift :: (a -> b) -> (a -> Nondet b)
(lift f) x = [f(x)]

Randomized

Function that uses randomization in its body, which requires a seed (for generating random numbers).

To avoid getting tangled in notation, it may be useful to remember that StdGen can be thought of as a Seed. Also MyRandom a describes a function that returns an element of type a and employs an element of randomness in its body (also returning a new Seed).

type Seed = StdGen  -- alias
type RandFunc a = Seed -> (a,Seed)  -- instead of MyRandom a

If you try to evaluate uniformNat on, say 5, instead of getting a random integer between 0 and 5 you will get an error:

> uniformNat 5
error:
   "No instance for (Show (RandFunc Int))"  -- says it can't print a function
   "Maybe you haven't applied a function to enough arguments?"  -- good question

Let’s see what uniformNat 5 actually is:

> :type (uniformNat 5)
(uniformNat 5) :: RandFunc Int

It is a function that returns an integer (and uses some kind of randomness inside)! Of course we can’t print it.

In order to get a result, we also have to pass a Seed to it. Luckily we have a seed defined for us - zeroSeed:

> (uniformNat 5) zeroSeed
(1, 13463877652103410263)  -- the generated integer is 1, the new seed is the long number