Introduction to Monads
======================
Announcements
-------------
* HW 3 available on the course website.
* Contact us if you can't see your grades online.
* Use Piazza
> {-# LANGUAGE NoImplicitPrelude, KindSignatures #-}
> module Monads where
> import Prelude hiding (filter)
> import Data.Char (toUpper)
> import Control.Monad (liftM, liftM2, guard)
Quiz
----
Consider the definition of trees with values at their *leaves*.
> data Tree a = Leaf a | Branch (Tree a) (Tree a)
> deriving (Eq, Show)
Define the following function that combines together the data stored
in the tree.
> -- | zip two trees together
> zipTree :: Tree a -> Tree b -> Tree (a,b)
> zipTree = undefined
o o o
/ \ / \ / \
"a" o ===> 0 o ===> ("a",0) o
/ \ / \ / \
"b" "c" 1 2 ("b",1) ("c", 2)
> testZip :: Bool
> testZip =
> zipTree (Branch (Leaf "a") (Branch (Leaf "b") (Leaf "c")))
> (Branch (Leaf 0 ) (Branch (Leaf 1 ) (Leaf 2 )))
> ==
> (Branch (Leaf ("a",0)) (Branch (Leaf ("b",1)) (Leaf ("c",2))))
Bonus points for defining this function in terms of a fold.
> t :: Tree String
> t = Branch (Leaf "a")
> (Branch (Leaf "b")
> (Leaf "c"))
Programming With Effects
========================
Shall we be pure or impure?
---------------------------
The functional programming community divides into two camps:
- "Pure" languages, such as Haskell, are based directly
upon the mathematical notion of a function as a
mapping from arguments to results.
- "Impure" languages, such as ML, are based upon the
extension of this notion with a range of possible
effects, such as exceptions and assignments.
Pure languages are easier to reason about and may benefit
from lazy evaluation, while impure languages may be more
efficient and can lead to shorter programs.
One of the primary developments in the programming language
community in recent years (starting in the early 1990s) has
been an approach to integrating the pure and impure camps,
based upon the notion of a "monad". This lecture introduces
the use of monads for programming with effects in Haskell.
However, although monads integrate "impure" programming into
Haskell, they are more general than that.
Abstracting programming patterns
================================
Monads are an example of the idea of abstracting out a common
programming pattern as a definition. Before considering monads,
let us review this idea, by thinking about error recovery.
How could we define zipTree so that we can recover from failure?
> zipTree1 :: Tree a -> Tree b -> Maybe (Tree (a,b))
> zipTree1 (Leaf a) (Leaf b) =
> Just (Leaf (a,b))
> zipTree1 (Branch l r) (Branch l' r') = undefined
> zipTree1 _ _ = Nothing
Yuck!
Do we need to use effects to write modular code???
Look closely at the program
---------------------------
zipTree :: Tree a -> Tree b -> Maybe (Tree (a,b))
zipTree (Leaf a) (Leaf b) =
Just (Leaf (a,b)) <-------------------- this is how we
zipTree (Branch l r) (Branch l' r') = return a value
~~~~~~~~~~~~~~~~~~~~~~ |
case zipTree l l' of |
Nothing -> Nothing |
Just l'' -> <------- This is how we |
~~~~~~~~~~~~~~~~~~~~ use a value |
case zipTree r r' of | |
Nothing -> Nothing <-----| |
Just r'' -> |
~~~~~~~~~~~~~~~~~~~~~~~ |
Just (Branch l'' r'') <------------------|
zipTree _ _ = Nothing
Common parts of the definition
------------------------------
* For returning a value we have:
Just (Leaf(a,b))
Just x
General pattern:
Just x
Give the pattern a good name:
return :: a -> Maybe a
return x = Just x
* For using a value we have:
case zipTree l l' of
Nothing -> Nothing
Just l'' -> ...
do something with l''
case zipTree r r' of
Nothing -> Nothing
Just r'' -> ...
do something with r''
General pattern:
case x of
Nothing -> Nothing
Just y -> f y
Name that pattern ("bind") or ("use x in f"):
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
x >>= f =
case x of
Nothing -> Nothing
Just y -> f y
That is, the first argument is `Nothing` then the second argument
is ignored and `Nothing` is returned as the result. Otherwise, if
the first argument is of the form `Just x`, then the second argument
is applied to `x` to give a result of type `Maybe b`.
Refactor the code!
------------------
Use the new general definitions to refactor the code.
> zipTree2 :: Tree a -> Tree b -> Maybe (Tree (a,b))
> zipTree2 (Leaf a) (Leaf b) = undefined
> zipTree2 (Branch l r) (Branch l' r') = undefined
> zipTree2 _ _ = Nothing
Do notation
-----------
Haskell provides a special notation for expressions of the above
structure, allowing them to be written in a more appealing form:
~~~~~{.haskell}
do x1 <- m1
x2 <- m2
...
xn <- mn
f x1 x2 ... xn
~~~~~
Hence, for example, our evaluator can be redefined as:
>
> zipTree3 :: Tree a -> Tree b -> Maybe (Tree (a,b))
> zipTree3 (Leaf a) (Leaf b) = do
> return (Leaf (a,b))
> zipTree3 (Branch l r) (Branch l' r') = do
> l'' <- zipTree3 l l';
> r'' <- zipTree3 r r';
> return (Branch l'' r'')
> zipTree3 _ _ = Nothing
>
Monads in Haskell
=================
The `do` notation for sequencing is not specific to the `Maybe` type,
but can be used with any type that forms a *monad*. The general
concept comes from a branch of mathematics called category theory.
In Haskell, however, a monad is simply a parameterised type `m`,
together with two functions of the following types:
~~~~~{.haskell}
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
~~~~~
(*Aside*: the two functions are also required to satisfy some simple
properties, but we will return to these later.) For example, if
we take `m` as the parameterised type `Maybe`, `return` as the function
`Just :: a -> Maybe a`, and `>>=` as defined in the previous section,
then we obtain our first example, called the *maybe monad*.
In fact, we can capture the notion of a monad as a new class
declaration. In Haskell, a class is a collection of types that
support certain overloaded functions. For example, the class
`Eq` of equality types can be declared as follows:
~~~~~{.haskell}
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x /= y = not (x == y)
~~~~~
The declaration states that for a type `a` to be an instance of
the class `Eq`, it must support equality and inequality operators
of the specified types. In fact, because a default definition
has already been included for `/=`, declaring an instance of this
class only requires a definition for `==`. For example, the type
`Bool` can be made into an equality type as follows:
~~~~~{.haskell}
instance Eq Bool where
False == False = True
True == True = True
_ == _ = False
~~~~~
The notion of a monad can now be captured as follows:
~~~~~{.haskell}
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
~~~~~
That is, a monad is a parameterised type `m` that supports `return`
and `>>=` functions of the specified types. The fact that `m` must
be a parameterised type, rather than just a type, is inferred from its
use in the types for the two functions. Using this declaration,
it is now straightforward to make `Maybe` into a monadic type:
~~~~~{.haskell}
instance Monad Maybe where
-- return :: a -> Maybe a
return x = Just x
-- (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= _ = Nothing
(Just x) >>= f = f x
~~~~~
(*Aside*: types are not permitted in instance declarations, but we
include them as comments for reference.) It is because of this
declaration that the `do` notation can be used to sequence `Maybe`
values. More generally, Haskell supports the use of this notation
with any monadic type. In the next few sections we give some
further examples of types that are monadic, and the benefits
that result from recognising and exploiting this fact.
The List Monad
==============
The `Maybe` monad provides a simple model of computations that can
fail, in the sense that a value of type `Maybe a` is either `Nothing`,
which we can think of as representing failure, or has the form
`Just x` for some `x` of type `a`, which we can think of as success.
The list monad generalises this notion, by permitting multiple
results in the case of success. More precisely, a value of
`[a]` is either the empty list `[]`, which we can think of as
failure, or has the form of a non-empty list `[x1,x2,...,xn]`
for some `xi` of type `a`, which we can think of as success.
A challenge
-----------
Write the function that considers each possible value `x`
from the list `xs`, and each value `y` from the list `ys`, and return
the pair `(x,y)`.
> pairs :: [Int] -> [Int] -> [(Int,Int)]
> pairs xs ys = undefined
>
> testPairs = pairs [1,2,3,4] [5,6,7,8]
Can we divide this up? What are the patterns here?
We have
concatMap (\x -> do something with x) xs
concatMap (\y -> do something with y) ys
Generalize to
concatMap f xs
Rejigger a bit to match the pattern above
(>>=) :: [a] -> (a -> [b]) -> [b]
xs >>= f = concatMap f xs
What could return be?
return :: a -> [a]
return x = [x]
Rewrite using >>= and return
> pairs1 :: [Int] -> [Int] -> [(Int,Int)]
> pairs1 xs ys = undefined
Rewrite using do notation
> pairs2 :: [Int] -> [Int] -> [(Int,Int)]
> pairs2 xs ys = undefined
> testPairs2 = pairs1 [1,2,3,4] [5,6,7,8]
Making lists into a monadic type is straightforward:
~~~~~{.haskell}
instance Monad [] where
-- return :: a -> [a]
return x = [x]
-- (>>=) :: [a] -> (a -> [b]) -> [b]
xs >>= f = concatMap f xs
~~~~~
(*Aside*: in this context, `[]` denotes the list type `[a]` without
its parameter.) That is, return simply converts a value into a
successful result containing that value, while `>>=` provides a
means of sequencing computations that may produce multiple
results: `xs >>= f` applies the function f to each of the results
in the list xs to give a nested list of results, which is then
concatenated to give a single list of results.
As a simple example of the use of the list monad, a function
that returns all possible ways of pairing elements from two
lists can be defined using the do notation as follows:
List comprehensions
-------------------
Recall the pairing function defined with the do notation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.{haskell}
pairs2 :: [Int] -> [Int] -> [(Int,Int)]
pairs2 xs ys = do x <- xs
y <- ys
return (x, y)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is interesting to note the similarity to how this function would be
defined using the list comprehension notation:
> pairs3 xs ys = [ (x,y) | x <- xs, y <- ys ]
What are some other examples that can be written using list comprehension?
* Rewrite the `map` function using a list comprehension.
> map' f xs = undefined
* Create a list of all pairs where the first component is less than the second.
> pairs4 xs ys = undefined
alternatively
> pairs4' xs ys = do x <- xs
> y <- ys
> if (x < y) then [ (x,y) ] else []
alternatively
> pairs4'' xs ys = do x <- xs
> y <- ys
> guard (x < y)
> return (x,y)
* Rewrite `filter`, using a guarded list comprehension.
> filter :: (a -> Bool) -> [a] -> [a]
> filter f xs = undefined
* Remember quicksort?
> quicksort [] = []
> quicksort (x:xs) = quicksort xs1 ++ [x] ++ quicksort xs2 where
> xs1 = [ y | y <- xs , y < x ]
> xs2 = [ y | y <- xs , y >= x ]
In fact, there is a formal connection between the `do` notation
and the comprehension notation. Both are simply different
shorthands for repeated use of the `>>=` operator for lists.
Indeed, the language *Gofer* that was one of the precursors
to Haskell permitted the comprehension notation to be used
with *any* monad. For simplicity however, Haskell only allows
the comprehension notation to be used with lists.
Credit
------
This lecture draws on lecture notes by [Graham Hutton][0] and [John
Hughes][1].
[0]: http://www.cs.nott.ac.uk/~gmh/monads
[1]: http://www.cse.chalmers.se/~rjmh/OPLSS/