Monad Lecture 2024-02-05

Table of Contents

See also: Understanding Monads

1. Resources

2. Tasks [2/2]

2.1. DONE How can I incorporate questions and interaction into this lesson?

2.2. DONE Build the slides

3. Brainstorm

Start with:

  • “Going from imprecise but accurate to precise and accurate”
  • Monads are interfaces
    • “wrap” and “thread”
    • example: Maybe
    • why do we care? generic interface
  • Monads as effects
    • What are effects?
    • How do Monads model effects?
  • More effects with Monads to handle them
    • List and non-determinism
    • Reader/Writer
  • Digression: typeclasses vs. interfaces
  • More monads
    • State
    • Useful monad functions
  • Combining monads
  • Alternatives to monads for handling effects

4. Learning Outcomes

  • Answer: what is a monad? (type + return + bind)
  • Answer: why do we have monads? (handle effects cleanly)
  • Answer: what are common monads? (Maybe, Either, List/Nondeterminism, IO)
  • Know: you can have more than return and bind operating on monads
  • Know: do notation is syntactic sugar

Stretch goals:

  • Know: difference between an interface and a typeclass
  • Know: how to handle effects in combination ← big stretch!

5. Outline (Draft 1)

5.1. What is a monad?

Caveat: I will be moving from an imprecise, intuitive, and accurate definition of a monad to a more precise, rigorous, and more accurate definition. Before you skewer me, be patient.

Monads, for all the mystery surrounding them, are really quite simple. If you’re thinking, “there’s got to be more to it than that”, well, not really. It’s just that the myriad applications of this simple thing is what is surprising.

5.1.1. Monad as an interface

A monad is an interface.

public interface Monad<T> {
    <R> Monad<R> thread(Function<T, Monad<R>> f);
    Monad<T> wrap(T value);
}
class Monad m where
    thread :: m a -> (a -> m b) -> m b
    wrap :: a -> m a

Any type can become a monad, provided it implements this interface.

data Foo a = Foo a
  deriving (Show)

instance Functor Foo where
  fmap f (Foo a) = Foo $ f a

instance Applicative Foo where
  pure = Foo
  (Foo a) <*> x = fmap a x

instance Monad Foo where
  return = pure
  Foo x >>= f = f x

Turns out, monads are also functors and applicatives—never mind—just know that they also need to implement fmap and <*> as well.

5.1.2. What are applicative functors?

Let’s start with the functor part: a functor is a fancy name for a container that you can map over. In Haskell land, we call this function fmap, but you can think of just implementing map.

What are applicatives? These generalize function application to work with things other than functions. You need two functions: pure, which just takes a value and wraps it in the data type, and apply or <*>.

5.2. Why do we have monads?

Monads are a handy way of modeling effects.

What? I don’t see how that from the interface.

Of course not—let’s look at an example.

5.2.1. What is an effect?

An effect is any observable behavior other than the return value.

Question: What are some effects?

  • failure/exception
  • IO
  • modifying state

Question: Why do you think effects might be something we want to worry about?

  • Breaks local reasoning
  • Side-effect-free functions are easier to test and compose
  • Easier to reason about under concurrency
  • Optimizations available
  • etc.

5.2.2. Encoding effects

Monads let us encode effects; let’s take a look at an example

5.2.3. Example: encoding failure with Maybe

data Maybe a = Nothing | Just a

instance Monad Maybe where
    return         = Just
    Nothing  >>= f = Nothing
    (Just x) >>= f = f x

instance MonadFail Maybe where
    fail _         = Nothing

instance MonadPlus Maybe where
    mzero             = Nothing
    Nothing `mplus` x = x
    x `mplus` _       = x

Monads are also useful to restrict access to a value outside a particular context.

Question: What have we gained by implementing Maybe?

5.3. What are some common monads?

  • Maybe
  • Either
  • State
  • Writer
  • Reader

5.4. What is a monad really?

5.4.1. Typeclasses vs Interfaces

Implementing a generic return is a little tricker if all we have are interfaces.

5.4.2. Coping in languages without proper typeclasses

We can use custom constructors—not as nice as return because we have to know exactly what monad it is we are operating in.

Rust doesn’t have typeclasses, but that doesn’t mean we can’t have Monads. Result and Option are monads!

Fun fact, Java’s optional breaks the monad laws by not being able to have null inside an optional.

Second option: delay wrapping. Racket’s monad library works by delaying what functor it uses with pure. From the docs:

Lifts a plain value into an applicative functor. When initially called, this function simply places the value in a box because it cannot yet know what kind of functor it needs to produce. When used, the value will be coerced into a functor of the appropriate type using the relevant value’s pure method.

https://docs.racket-lang.org/functional/interfaces.html#%28def._%28%28lib._data%2Fapplicative..rkt%29._pure%29%29

5.5. Using monads comfortably

5.5.1. The do notation

This:

do
  x <- Just 42
  y <- Just (x + 1)
  Just (y * 2)

is the same as this:

Just 42 >>= (\x -> (Just (x + 1)) >>= (\y -> Just (y * 2)))

5.5.2. Other helpful functions

  • liftM
  • fmap
  • <*> (from the Applicative type class)
  • (Monad m) => m (m a) → m a join (fmap f m)m >>= f
  • filterM
  • mapM
  • foldM
liftM :: Monad m => (a1 -> r) -> m a1 -> m r
fmap :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
join :: Monad m => m (m a) -> m a
mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)
filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a]
foldM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b

5.6. More advanced macro example: nondeterminism

Walk through how allSolutions works.

5.7. Practice: state and random number generation

Let’s write a linear congruence generator

\[ X_{n+1} = (aX_n + c) \mod m \]

where

\(m\), \(0 < m\)
the modulus
\(a\), \(0 < a < m\)
the multiplier
\(c\), \(0 \leq c < m\)
the increment
\(X_0\), \(0 \leq X_0 < m\)
the seed or start value

are integer constants that specify the generator.

linearCongruence :: Int -> Int
linearCongruence seed =
  (75 * seed + 74) `mod` (2^16 + 1)

threeFlips :: Seed -> (Bool, Bool, Bool)
threeFlips s =
  let (firstCoin, s') = randCoin s
      (secondCoin, s'') = randCoin s'
      (thirdCoin, s''') = randCoin s'' in
    (firstCoin, secondCoin, thirdCoin)

But it’s cumbersome to thread the state of the random number generator. We can use a monad to make it more convenient.

First, the type of the state monad is s -> (a, s), where s is some state, and a is the result of running the stateful computation.

5.8. Monad laws

These are important to know if you make your own monad. They are simple and fairly straight-forward, but they will help you determine how your monad operates.

These are not enforced by the compiler!

How return and bind interact:

return x >>= f
-- is the same as
f x

x >>= return
-- is the same as
x

How bind chains:

x >>= f >>= g
-- explicitly parenthesized is
(x >>= f) >>= g
-- which is the same as
x >>= (\y -> (f y) >>= g)

Date: 2024-01-31 Wed 00:00

Author: Ashton Wiersdorf

Created: 2024-02-05 Mon 17:05

Validate