monads

back

the programming language used here is called haskell. haskell isn't quite 100% identical to the math behind the concepts, but (1) we can pretend it is and be fine, and (2) if it *were* identical to the math, haskell wouldn't actually be able to do everything a computer can do! for example functions in haskell (and in programming in general) might go on forever or end without producing any result, which isn't allowed for functions in math. but anyway

a functor is kind of like a box. unlike boxes in real life, once you put a thing (a) in a box to get a thing-in-a-box (f a), you can do stuff to it, like turning it into a different-thing-in-a-box (f b), without needing to take it out of the box. we call doing that fmap.

fmap :: Functor f => (a -> b) -> f a -> f b

an example functor is a box that Maybe has something in it. it might have Nothing in it or it might have Just a value in it. we can fmap over it just fine:

fmap (1 +) (Just 5) == Just 6
fmap (1 +) Nothing == Nothing

if you like fancy symbols you can spell fmap as <$>.

what if you have multiple boxes and you want to do something to both of them such that you end up with only one box? then fmap isn't enough: it can only do stuff to one box at a time. so we need an applicative functor. applicative functors can apply boxed functions to boxed values. we call doing that <*>.

(<*>) :: f (a -> b) -> f a -> f b

we also need a way to put stuff into a box regardless of what kind of box it is, which will be helpful later

pure :: Applicative f => a -> f a

now we can do things like

pure (+) <*> Just 2 <*> Just 3 == Just 5
pure (+) <*> Just 2 <*> Nothing == Nothing

pure f <*> x is actually equivalent to f <$> x (can you see why?)

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

not every functor is an applicative functor. maybe your box has a permanent unchangeable label:

data Labeled a = Labeled String a -- for example, Labeled "my box" 2

this is a functor, because we can define e.g.

instance Functor Labeled where
  fmap f (Labeled label x) = Labeled label (f x)

but it's not an applicative functor: if you have two boxes, maybe "my box" and "your box", and you try to add them, what should the label of the resulting box be? "my box"? "your box"? maybe "our box"? there isn't really an obvious way

what if the stuff you want to do creates a brand new box?

half :: Int -> Maybe Int
half x = if even x then Just (x `div` 2) else Nothing

fmapping this won't go so well.

half <$> Just 10 == Just (Just 5)

a monad is an applicative functor that can join nested boxes.

join :: Monad m => m (m a) -> m a
join (Just (Just 5)) == Just 5
join (Just Nothing) == Nothing
join Nothing == Nothing

we also define a shortcut for fmap-and-join, "bind":

(>>=) :: Monad m => m a -> (a -> m b) -> m b

since all monads are applicative functors, we don't need a new way to put something into a monad. haskell used to make you define "return" but it's identical to pure so we don't really need to make it explicit. but now we can do stuff like

Just 10 >>= half == Just 5
Just 9 >>= half == Nothing

and we can chain them!

Just 20
  >>= half -- Just 10
  >>= half -- Just 5
  >>= half == Nothing

writing >>= a lot can be annoying. so haskell gives us special syntax to make long bind chains look more 'normal'. for example this does the exact same thing:

divideby8 :: Int -> Maybe Int
divideby8 x = do
  y <- half x
  z <- half y
  half z
divideby8 20 == Nothing