Joseph Haugh
University of New Mexico
Remember this problem?
data Term = PrimApp PrimOp [Term]
data PrimOp = Zero | Succ | Sub
hcc :: Term -> (Term -> Term) -> Term
hcc t kappa = case t of
PrimApp p [] ->
kappa (PrimApp p [])
PrimApp p [t1] ->
hcc t1 (\v1 -> kappa (PrimApp p [v1]))
PrimApp p [t1, t2] ->
hcc t1 (\v1 -> hcc t2 (\v2 -> kappa (PrimApp p [v1, v2])))
This week we are going to tackle this problem and by the end we will have a 1 line solution!
What do I mean by “return to your caller”?
id :: a -> a
id x = x
id
is written in direct style, because it returns x
directly back to the caller of the function.
How could I get around that?
I need to be given an explicit function to return to:
idCPS :: a -> (a -> a) -> a
idCPS x _return = _return x
idCPS
is written in continuation passing style(CPS), because it returns or continues with the provided function.
id :: a -> a
id x = x
Ask yourself, how does the program continue after id returns x?
The answer is completely up to the caller.
ghci> length (id [1,2,3])
3
ghci> putStrLn (id "hello world")
hello world
idCPS :: a -> (a -> a) -> a
idCPS x _return = _return x
Ask yourself again, how does the program continue after idCPS
calls _return
?
The answer of how to continue is completely up to the function passed in, _return
, not the caller. This function is called a continuation.
ghci> idCPS [1,2,3] length
Type Error!
ghci> idCPS "hello world" putStrLn
Type Error!
Why do these not work??
Our type is too specific!
idCPS
idCPS :: a -> (a -> a) -> a
idCPS x _return = _return x
How can we generalize this?
Notice that the return type is entirely determined by whatever _return
’s return type is. Thus, we could change the type of idCPS
to be as follows:
idCPS :: a -> (a -> r) -> r
idCPS x _return = _return x
ghci> idCPS [1,2,3] length
3
ghci> idCPS "hello world" putStrLn
hello world
Direct:
id :: a -> a
id x = x
CPS:
idCPS :: a -> (a -> r) -> r
idCPS x _return = _return x
Lets try to calculate the triangle number of a given Int
.
The triangle number is given by the following formula:
$$ t_n = \begin{cases} 1, & \text{if } n = 1 \\ n+ t_{n - 1}, & \text{if } n > 1 \end{cases} $$
triangle :: Int -> Int
triangle 1 = 1
triangle x = x + triangle (x - 1)
Can we write this in CPS style?
You might try to follow the pattern so far and write this:
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return = x + _return (x - 1)
Will this work? Only one way to find out!
ghci> :r
Type Error!
Why do we get a type error?!
x :: Int
_return (x - 1) :: r
.
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return = x + _return (x - 1)
This code is also breaking our cardinal rule: No direct returning allowed
We are also not recursing!
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return = x + _return (x - 1)
Lets try to fix the recursion problem first by just putting in a recursive call.
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return = triangleCPS ???1 ???2
What do we need to put for ???1?
Well we need to recurse with a smaller value, in this case x - 1.
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return = triangleCPS (x - 1) ???2
What about for ???2?
Well we know it needs to be a function of 1 argument…
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return = triangleCPS (x - 1) (\result -> ???)
Why do I call the argument result?
What does ??? need to be?
Well it needs to be our actual algorithm, we add the current value of x to the result of recursing with x - 1.
We then give this to _return
because we are still not allowed to directly return.
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return = triangleCPS (x - 1) (\result -> _return (x + result))
At each recursive call we need to provide a continuation for that recursive call to continue with.
We then call our continuation, _return
, on the x + result.
Lets test it:
ghci> triangleCPS 5 id
15
Why is it that I am passing id
to this function?
I am telling triangleCPS how to continue after it produces its final result.
triangle :: Int -> Int
triangle 1 = 1
triangle x = x + triangle (x - 1)
This probably confused you at first too.
Then you became comfortable with a recursion simply representing the answer to the question posed by its arguments.
triangle (x - 1) can be thought of as the triangular number of x - 1.
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return = triangleCPS (x - 1) (\result -> _return (x + result))
Now you have a new mountain to climb, understanding CPS!
However, it is not as hard as it seems.
The biggest difference is that the recursive call doesn’t directly return to us a result.
Instead it returns to the continuation function we provide!
That is why I call the parameter result, it is the result of the recursive call!
Lets see if we can gain some clarity by expanding it out.
triangle :: Int -> Int
triangle 1 = 1
triangle x = x + triangle (x - 1)
triangle 3
| { applying triangle }
3 + triangle (2)
| { applying triangle }
3 + 2 + triangle 1
| { applying triangle }
3 + 2 + 1
| { applying (+) }
6
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return = triangleCPS (x - 1) (\res -> _return (x + res))
triangleCPS 3 id
| { applying triangleCPS }
triangleCPS (3 - 1) (\res1 -> id (3 + res1))
| { applying (-) and triangleCPS }
triangleCPS (2 - 1) (\res2 -> (\res1 -> id (3 + res1)) (2 + res2))
| { applying (-) and triangleCPS }
(\res2 -> (\res1 -> id (3 + res1)) (2 + res2)) 1
| { applying outer lambda }
(\res1 -> id (3 + res1)) (2 + 1)
| { applying lambda }
id (3 + 2 + 1)
| { applying (+) and id }
6
A function is tail recursive when all of its recursive calls are in /tail position.
An expression is in tail position if before the function returns nothing else needs to be evaluated.
For example the following places marked, (tp)
, are tail positions:
f x = (tp)
if b then (tp) else (tp)
let e in (tp)
case e of
p1 -> (tp)
p2 -> (tp)
Our direct definition of triangle is not tail recursive:
triangle :: Int -> Int
triangle 1 = 1
triangle x = x + triangle (x - 1)
Whereas, our CPS version is:
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return = triangleCPS (x - 1) (\res -> _return (x + res))
In fact all functions written in CPS are tail recursive!
Okay, so what?
Tail recursive functions, if optimized by the compiler, can be run in constant stack space.
No stack overflow errors!
A function in tail position has complete control over what happens next.
Direct:
id :: a -> a
id x = x
triangle :: Int -> Int
triangle 1 = 1
triangle x =
x + triangle (x - 1)
CPS:
idCPS :: a -> (a -> r) -> r
idCPS x _return = _return x
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return =
triangleCPS (x - 1) (\res -> _return (x + res))
Lets write a program which takes in a triple of Ints and computes the triangular number of each and then returns the sum of these numbers.
Direct Implementation:
triangle3 :: (Int, Int, Int) -> Int
triangle3 (x, y, z) =
let xt = triangle x
yt = triangle y
zt = triangle z
in xt + yt + zt
Now I would like you to write this same function but using triangleCPS instead.
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return =
triangleCPS (x - 1) (\res -> _return (x + res))
triangle3CPS :: (Int, Int, Int) -> (Int -> r) -> r
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return =
triangleCPS (x - 1) (\res -> _return (x + res))
triangle3CPS :: (Int, Int, Int) -> (Int -> r) -> r
triangle3CPS (x, y, z) _return =
triangleCPS x (\xt ->
triangleCPS y (\yt ->
triangleCPS z (\zt ->
_return (xt + yt + zt))))
The Fibonacci sequence is defined as follows:
$$ f_n = \begin{cases} 1, & \text{if } n = 0 \\ 1, & \text{if } n = 1 \\ f_{n-1} + f_{n-2}, & \text{if } n > 1 \end{cases} $$
fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
Try to define fibCPS.
fibCPS :: Int -> (Int -> r) -> r
Try to define fibCPS.
fibCPS :: Int -> (Int -> r) -> r
fibCPS 0 _return = _return 1
fibCPS 1 _return = _return 1
fibCPS n _return =
fibCPS (n - 1) (\result1 ->
fibCPS (n - 2) (\result2 ->
_return (result1 + result2)))
Notice that in this code the intermediate results are made explicit, result1 and result2.
fibCPS :: Int -> (Int -> r) -> r
fibCPS 0 _return = _return 1
fibCPS 1 _return = _return 1
fibCPS n _return =
fibCPS (n - 1) (\result1 ->
fibCPS (n - 2) (\result2 ->
_return (result1 + result2)))
We can of course also do this in the direct version as well:
fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n =
let result1 = fib (n - 1)
result2 = fib (n - 2)
in result1 + result2
However, something we cannot as easily do in the direct version is make the order of evaluation explicit.
Notice in the CPS version we know exactly how the code will be evaluated:
fibCPS :: Int -> (Int -> r) -> r
fibCPS 0 _return = _return 1
fibCPS 1 _return = _return 1
fibCPS n _return =
fibCPS (n - 1) (\result1 ->
fibCPS (n - 2) (\result2 ->
_return (result1 + result2)))
fibCPS :: Int -> (Int -> r) -> r
fibCPS 0 _return = _return 1
fibCPS 1 _return = _return 1
fibCPS n _return =
fibCPS (n - 1) (\result1 ->
fibCPS (n - 2) (\result2 ->
_return (result1 + result2)))
fibCPS 2 id
| { applying fibCPS }
fibCPS (2 - 1) (\result1 ->
fibCPS (2 - 2) (\result2 ->
id (result1 + result2)))
| { applying (-) and outer fibCPS }
(\result1 -> fibCPS (2 - 2) (\result2 ->
id (result1 + result2))) 1
| { applying lambda }
fibCPS (2 - 2) (\result2 ->
id (1 + result2))
| { applying (-) and fibCPS }
(\result2 ->
id (1 + result2)) 1
| { applying lambda }
id (1 + 1)
| { applying (+) and id }
2
We never had a choice about what to evaluate first.
Whereas, with fib we do. To illustrate this lets walk through the evaluation of fib 2:
Two choices, same result
fib 2
| { applying fib }
fib (2 - 1) + fib (2 - 2)
| { applying (-) and first fib }
1 + fib (2 - 2)
| { applying (-) and second fib }
1 + 1
| { applying (+) }
2
fib 2
| { applying fib }
fib (2 - 1) + fib (2 - 2)
| { applying (-) and second fib }
fib (2 - 1) + 1
| { applying (-) and first fib }
1 + 1
| { applying (+) }
2
This doesn’t happen with CPS, there is single way to evaluate a CPS program.
Direct:
id :: a -> a
id x = x
triangle :: Int -> Int
triangle 1 = 1
triangle x =
x + triangle (x - 1)
fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n =
let result1 = fib (n - 1)
result2 = fib (n - 2)
in result1 + result2
CPS:
idCPS :: a -> (a -> r) -> r
idCPS x _return = _return x
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return =
triangleCPS (x - 1) (\res -> _return (x + res))
fibCPS :: Int -> (Int -> r) -> r
fibCPS 0 _return = _return 1
fibCPS 1 _return = _return 1
fibCPS n _return =
fibCPS (n - 1) (\result1 ->
fibCPS (n - 2) (\result2 ->
_return (result1 + result2)))
So far it seems like the general shape of CPS function is:
a -> (a -> r) -> r
Is this sufficiently general though?
What if we wanted to define the length function in CPS?
length :: [a] -> Int
length [] = 0
length (x:xs) = 1 + length xs
lengthCPS :: [a] -> (??? -> r) -> r
What type should the ??? be?
If we follow the pattern it should be [a], lets try to define it with that.
lengthCPS :: [a] -> ([a] -> r) -> r
lengthCPS [] _return = _return 0
We already have a problem, 0 is not of type [a]!
This further reinforces what the argument to the continuation means, it is the result of the current computation.
The result of length is an Int, thus we need to further generalize our notion of a CPS function to:
a -> (b -> r) -> r
We can now finish writing lengthCPS:
lengthCPS :: [a] -> (Int -> r) -> r
lengthCPS [] _return = _return 0
lengthCPS (x:xs) _return = lengthCPS xs (\result -> _return (1 + result))
Direct:
fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n =
let result1 = fib (n - 1)
result2 = fib (n - 2)
in result1 + result2
length :: [a] -> Int
length [] = 0
length (x:xs) =
let result = length xs
in 1 + result
CPS:
fibCPS :: Int -> (Int -> r) -> r
fibCPS 0 _return = _return 1
fibCPS 1 _return = _return 1
fibCPS n _return =
fibCPS (n - 1) (\result1 ->
fibCPS (n - 2) (\result2 ->
_return (result1 + result2)))
lengthCPS :: [a] -> (Int -> r) -> r
lengthCPS [] _return = _return 0
lengthCPS (x:xs) _return =
lengthCPS xs (\result ->
_return (1 + result))
id :: a -> a
id x = x
idCPS :: a -> (a -> r) -> r
idCPS x _return = _return x
triangle :: Int -> Int
triangle 1 = 1
triangle x =
x + triangle (x - 1)
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return =
triangleCPS (x - 1) (\result -> _return (x + result))
fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n =
let result1 = fib (n - 1)
result2 = fib (n - 2)
in result1 + result2
fibCPS :: Int -> (Int -> r) -> r
fibCPS 0 _return = _return 1
fibCPS 1 _return = _return 1
fibCPS n _return =
fibCPS (n - 1) (\result1 ->
fibCPS (n - 2) (\result2 ->
_return (result1 + result2)))
fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n =
let result1 = fib (n - 1)
result2 = fib (n - 2)
in result1 + result2
fibCPS :: Int -> (Int -> r) -> r
fibCPS 0 _return = _return 1
fibCPS 1 _return = _return 1
fibCPS n _return =
fibCPS (n - 1) (\result1 ->
fibCPS (n - 2) (\result2 ->
_return (result1 + result2)))
data Term = PrimApp PrimOp [Term]
data PrimOp = Zero | Succ | Sub
hcc :: Term -> (Term -> Term) -> Term
hcc t _return = case t of
PrimApp p [] ->
_return (PrimApp p [])
PrimApp p [t1] ->
hcc t1 (\v1 -> _return (PrimApp p [v1]))
PrimApp p [t1, t2] ->
hcc t1 (\v1 -> hcc t2 (\v2 -> _return (PrimApp p [v1, v2])))
Perhaps this function, hcc, might make more sense now.
data Term = PrimApp PrimOp [Term]
data PrimOp = Zero | Succ | Sub
hcc :: Term -> (Term -> Term) -> Term
hcc t _return = case t of
PrimApp p [] ->
_return (PrimApp p [])
PrimApp p [t1] ->
hcc t1 (\v1 -> _return (PrimApp p [v1]))
PrimApp p [t1, t2] ->
hcc t1 (\v1 -> hcc t2 (\v2 -> _return (PrimApp p [v1, v2])))
hcc compiles a Term t and continues with _return
when it is done.
hcc is of course written in CPS.
What would it look like when written in direct style?
data Term = PrimApp PrimOp [Term]
data PrimOp = Zero | Succ | Sub
-- Here is an example term, 1 - (1 - 0)
example1 :: Term
example1 =
PrimApp Sub [PrimApp Succ [PrimApp Zero []],
PrimApp Sub [
PrimApp Succ [PrimApp Zero []],
PrimApp Zero []]]
dd :: Term -> Term
dd t = case t of
PrimApp p [] -> PrimApp p []
PrimApp p [t1] -> (\v1 -> PrimApp p [v1]) (dd t1)
PrimApp p [t1, t2] -> (\v1 v2 -> PrimApp p [v1, v2]) (dd t1) (dd t2)
PrimApp p [t1, t2, t3] ->
(\v1 v2 v3 -> PrimApp p [v1, v2, v3]) (dd t1) (dd t2) (dd t3)
data Term = PrimApp PrimOp [Term]
data PrimOp = Zero | Succ | Sub
-- 1 - (1 - 0)
example1 :: Term
example1 =
PrimApp Sub [PrimApp Succ [PrimApp Zero []],
PrimApp Sub [
PrimApp Succ [PrimApp Zero []],
PrimApp Zero []]]
dd :: Term -> Term
dd (PrimApp p ts) = PrimApp p (map dd ts)
Its just map! Aside: where is the base case!?
This begs the question, can we solve this with CPS map?
What does the type of mapCPS need to be?
We need to make sure that the function we are given is also in CPS!
mapCPS :: (a - > (b -> r) -> r) -> [a] -> ([b] -> r) -> r
mapCPS :: (a -> (b -> r) -> r) -> [a] -> ([b] -> r) -> r
mapCPS f [] _return = _return []
mapCPS f (x:xs) _return =
f x (\x' ->
mapCPS f xs (\xs' ->
_return (x':xs')))
Not too bad!
Now can we use this to solve the original problem??
hccMapCPS :: Term -> (Term -> Term) -> Term
hccMapCPS (PrimApp p ts) _return =
mapCPS hccMapCPS ts (\ts' ->
_return (PrimApp p ts'))
Short and sweet.
No but really that is basically a 1 line solution which naturally arises when you abide by the fact that CPS is supposed to be a global transformation.
I credit Darko for showing me this solution.
When I was working on this problem I of course didn’t think of this, instead I struggled against the types mightily but I think the payoff was worth it.
To start lets talk about my first approximation of a solution which likely most if not all of you also came up with.
hcc :: Term -> (Term -> Term) -> Term
hcc (PrimApp p ts) _return = go [] ts
where
go :: [Term] -> [Term] -> Term
go vals [] = _return (PrimApp p (reverse vals))
go vals (t:ts) =
hcc t (\v -> go (v:vals) ts)
Interestingly, go itself is not a CPS function, instead it builds a CPS expression using hcc.
I knew deep down that go must be able to written as fold.
Lets revisit an old favorite function, the lowly foldr:
foldr :: (a -> b -> b) -> b -> [a] -> b
Aside: do you have any new appreciation for foldr?
As I always say in 357, when writing with folds, always know what your b is!
What is our b here?
Well ultimately we want to return Term, but how then do we buildup the [Term]?
Your first thought might be to use a tuple to buildup the [Term] while also building up the Term.
Lets try that:
hccFold :: Term -> (Term -> Term) -> Term
hccFold (PrimApp p ts) _return = fst $ foldr go (???1, []) ts
where
go t (???2, vs) = (hcc t (\v -> ???3), ???4:vs)
We can’t make it work it this way since we need to build the [Term] and the Term together not separately.
The trick we used without foldr was to add an extra parameter to our function. Can we do the same with foldr?
What if we try to make our b, [Term] -> Term
, effectively adding an extra parameter!
Lets see what the type of foldr would look like with that as the b:
foldr :: (a -> b -> b) -> b -> [a] -> b
The trick we used without foldr was to add an extra parameter to our function. Can we do the same with foldr?
What if we try to make our b, [Term] -> Term
, effectively adding an extra parameter!
Lets see what the type of foldr would look like with that as the b:
foldr :: (a -> ([Term] - > Term) -> ([Term] -> Term)) ->
([Term] -> Term) ->
[a] ->
([Term] -> Term)
foldr :: (a -> ([Term] - > Term) -> ([Term] -> Term)) ->
([Term] -> Term) ->
[a] ->
([Term] -> Term)
hccFold :: Term -> (Term -> Term) -> Term
hccFold (PrimApp p ts) _return =
foldr go ??? ts
where
-- ( a) -> ( b ) -> ( b )
go :: Term -> ([Term] -> Term) -> ([Term] -> Term)
go = ???
foldr :: (a -> ([Term] - > Term) -> ([Term] -> Term)) ->
([Term] -> Term) ->
[a] ->
([Term] -> Term)
hccFold :: Term -> (Term -> Term) -> Term
hccFold (PrimApp p ts) _return =
foldr go (\vs -> _return (PrimApp p (reverse vs))) ts []
where
-- ( a) -> ( b ) -> ( b )
go :: Term -> ([Term] -> Term) -> ([Term] -> Term)
go t _rest = \vs -> hccFold t (\v -> _rest (v:vs))
foldr :: (a -> ([Term] - > Term) -> ([Term] -> Term)) ->
([Term] -> Term) ->
[a] ->
([Term] -> Term)
hccFold :: Term -> (Term -> Term) -> Term
hccFold (PrimApp p ts) _return =
foldr go (_return . PrimApp p . reverse) ts []
where
-- ( a) -> ( b ) -> ( b )
go :: Term -> ([Term] -> Term) -> ([Term] -> Term)
go t _rest = \vs -> hccFold t (\v -> _rest (v:vs))
Awesome!!
Is foldr really what we want though?
Remember that CPS makes the order of evaluation explicit what is the order of evaluation when using foldr?
Right to left, we don’t necessarily want that, with map it is applying the functions left to right.
We could instead just use foldl!
This also means no reverse!
hccFold :: Term -> (Term -> Term) -> Term
hccFold (PrimApp p ts) _return = foldl go (_return . PrimApp p) ts []
where
-- ( b ) -> ( a) -> ( b )
go :: ([Term] -> Term) -> Term -> ([Term] -> Term)
go _acc t = \vs -> hccFold t (\v -> _acc (v:vs))
Clean.
But we can do better!
Recall our definition of fibCPS:
fibCPS :: Int -> (Int -> r) -> r
fibCPS 0 _return = _return 1
fibCPS 1 _return = _return 1
fibCPS n _return =
fibCPS (n - 1) (\result1 ->
fibCPS (n - 2) (\result2 ->
_return (result1 + result2)))
Does this remind you of anything?
What about our generalized type of a CPS function?
a -> (b -> r) -> r
What does it mean for a function to be partial?
What is the name of a function which is not partial?
Total!
Now lets imagine that you want to stamp out partial functions from your standard library entirely!
You would probably start with head and tail
ghci> head []
Runtime Error!
ghci> tail []
Runtime Error!
How can we make these functions total?
One way is to add additional structure to the input type.
Instead of accepting a plain list they could take in a NonEmpty list, this is a topic for another day.
Another way is by adding additional structure to the output type:
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:xs) = Just x
safeTail :: [a] -> Maybe [a]
safeTail [] = Nothing
safeTail (x:xs) = Just xs
Then, because we want to write Lisp style programs, we want to define functions to get the second and third elements of a list.
Writing these using the unsafe head and tail is trivial:
second = head . tail
third = head . tail . tail
Writing these with safeHead and safeTail is… ugly:
safeSecond :: [a] -> Maybe a
safeSecond xs =
case safeTail xs of
Nothing -> Nothing
Just xs' -> safeHead xs'
safeThird :: [a] -> Maybe a
safeThird xs =
case safeTail xs of
Nothing -> Nothing
Just xs' ->
case safeTail xs' of
Nothing -> Nothing
Just xs'' -> safeHead xs''
Lets take another look at safeSecond:
safeSecond :: [a] -> Maybe a
safeSecond xs =
case safeTail xs of
Nothing -> Nothing
Just xs' -> safeHead xs'
Can you spot the pattern?
Of course the function we are looking for is bind (>>=):
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Aside: Does that type signature look familiar?
We can then rewrite safeSecond and safeThird as follows:
safeSecond xs = safeTail xs >>= safeHead
safeThird xs = safeTail xs >>= safeTail >>= safeHead
Do you spot the similarity yet?
What if I expand out the definition of safeSecond and safeThird a bit:
safeSecond xs =
safeTail xs >>= (\xs' ->
safeHead xs')
safeThird xs =
safeTail xs >>= (\xs' ->
safeTail xs' >>= (\xs'' ->
safeHead xs''))
What if I expand out the definition of safeSecond and safeThird a bit:
safeSecond xs =
safeTail xs >>= (\xs' ->
safeHead xs')
safeThird xs =
safeTail xs >>= (\xs' ->
safeTail xs' >>= (\xs'' ->
safeHead xs''))
fibCPS :: Int -> (Int -> r) -> r
fibCPS 0 _return = _return 1
fibCPS 1 _return = _return 1
fibCPS n _return =
fibCPS (n - 1) (\result1 ->
fibCPS (n - 2) (\result2 ->
_return (result1 + result2)))
What if I expand out the definition of safeSecond and safeThird a bit:
safeSecond xs =
safeTail xs >>= (\xs' ->
safeHead xs')
safeThird xs =
safeTail xs >>= (\xs' ->
safeTail xs' >>= (\xs'' ->
safeHead xs''))
(>>=) :: m a -> (a -> m b) -> m b
fibCPS :: Int -> (Int -> r) -> r
fibCPS 0 _return = _return 1
fibCPS 1 _return = _return 1
fibCPS n _return =
fibCPS (n - 1) (\result1 ->
fibCPS (n - 2) (\result2 ->
_return (result1 + result2)))
cpsFunc :: a -> (b -> r) -> r
(>>=) is a restricted CPS function!
triangleCPS :: Int -> (Int -> r) -> r
triangleCPS 1 _return = _return 1
triangleCPS x _return =
triangleCPS (x - 1) (\result -> _return (x + result))
The result of triangleCPS (x - 1) will be given to the continuation and bound to result
safeSecond :: [a] -> Maybe a
safeSecond xs =
safeTail xs >>= (\xs' ->
safeHead xs')
The result of safeTail xs, if it is not Nothing, will be given to the continuation and bound to xs'
, otherwise, Nothing will be passed through.
In fact the Maybe monad embodies one of the CPS powers I didn’t explicitly discuss previously, the ability to escape the computation at any time.
This is how safeSecond and safeThird behave but in reality the computation still continues but effectively nothing more will be done once a function in the bind chain returns Nothing
If we have time at the end we will rewrite these functions using CPS directly!
All this begs the question, is there a monad here?
Of course!
But… what does it look like?
Remind yourself of the general form of a CPS function:
a -> (b -> r) -> r
Hmm lets remind ourselves of how we might have discovered the State monad.
We find ourselves writing a lot of function which have to explicitly thread the state such as:
a -> s -> (b, s)
So we say the State monad is the effect being returned, the s -> (a, s)
What is it for continuations then?
(b -> r) -> r
(a -> r) -> r
There it is!
Now we can wrap it in a newtype and start building up to a Monad instance!
newtype Cont r a = Cont { runCont :: (a -> r) -> r }
This is how you will find it defined most places.
Ponder for a moment the type of runCont:
ghci> :t runCont
Cont r a -> (a -> r) -> r
What does that remind you of?
It looks like (>>=) :: m a -> (a -> m b) -> m b
!
So instead I will write it like this:
newtype Cont r a = Cont { (>>-) :: (a -> r) -> r }
Try to write the Functor instance for Cont:
newtype Cont r a = Cont { (>>-) :: (a -> r) -> r }
instance Functor (Cont r) where
fmap f cnta = ???
Try to write the Functor instance for Cont:
newtype Cont r a = Cont { (>>-) :: (a -> r) -> r }
instance Functor (Cont r) where
fmap f cnta =
Cont $ \_return ->
cnta >>- \a ->
_return (f a)
Try to write the Applicative instance for Cont:
newtype Cont r a = Cont { (>>-) :: (a -> r) -> r }
instance Applicative (Cont r) where
pure x = ???1
cntf <*> cnta = ???2
Try to write the Applicative instance for Cont:
newtype Cont r a = Cont { (>>-) :: (a -> r) -> r }
instance Applicative (Cont r) where
pure x =
Cont $ \_return ->
_return x
cntf <*> cnta = ???2
Try to write the Applicative instance for Cont:
newtype Cont r a = Cont { (>>-) :: (a -> r) -> r }
instance Applicative (Cont r) where
pure x =
Cont $ \_return ->
_return x
cntf <*> cnta =
Cont $ \_return ->
cntf >>- \f ->
cnta >>- \a ->
_return (f a)
Try to write the Monad instance for Cont:
newtype Cont r a = Cont { (>>-) :: (a -> r) -> r }
instance Monad (Cont r) where
return x = ???1
cnta >>= f = ???2
Try to write the Monad instance for Cont:
newtype Cont r a = Cont { (>>-) :: (a -> r) -> r }
instance Monad (Cont r) where
return x =
Cont $ \_return ->
_return x
cnta >>= f = ???2
Try to write the Monad instance for Cont:
newtype Cont r a = Cont { (>>-) :: (a -> r) -> r }
instance Monad (Cont r) where
return x =
Cont $ \_return ->
_return x
cnta >>= f =
Cont $ \_return ->
cnta >>- \a ->
f a >>- \b ->
_return b
a
and (a -> r) -> r
Now that we know that there exists a Cont monad, we only need one more thing for our ultimate 1 line solution…
Have you used much of the function traverse
, or its less general cousin mapM
?
If not, hopefully you will more after today!
After I learned about map
I start to see it everywhere and found that it underlied most abstractions.
After I learned about traverse
I also started to see it everywhere and found that it underlied most monadic abstractions.
Lets take a look at what Traversable offers us:
ghci> :i Traversable
type Traversable :: (* -> *) -> Constraint
class (Functor t, Foldable t) => Traversable t where
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
sequenceA :: Applicative f => t (f a) -> f (t a)
mapM :: Monad m => (a -> m b) -> t a -> m (t b)
sequence :: Monad m => t (m a) -> m (t a)
{-# MINIMAL traverse | sequenceA #-}
To start to get an intuitive understand of traverse tell me what type the following expression will have:
fmap :: Functor f => (a -> b) -> [a] -> [b]
foo :: Applicative f => a -> f b
xs :: [a]
fmap foo xs :: ???
To start to get an intuitive understand of traverse tell me what type the following expression will have:
fmap :: Functor f => (a -> b) -> [a] -> [b]
foo :: Applicative f => a -> f b
xs :: [a]
fmap foo xs :: [f b]
Imagine you are trying to read in a list of files, [String], using readFile :: String -> IO String
.
files :: [String]
readFile :: String -> IO String
fmap readFile files :: ???
Imagine you are trying to read in a list of files, [String], using readFile :: String -> IO String
.
files :: [String]
readFile :: String -> IO String
fmap readFile files :: [IO String]
[IO String] is probably not what you want because then you can’t use <- to get out the list of file contents in the function you are writing.
Instead you really want IO [String].
You effectively want to swap the list and the IO.
This is where sequence comes in!
sequence :: Monad m => t (m a) -> m (t a)
traverse f ls === sequenceA (fmap f ls)
mapM f ls === sequence (fmap f ls)
The only difference between traverse and mapM is the Applicative vs Monad constraint.
Write the following function:
sequenceList :: Monad m => [m a] -> m [a]
sequenceList = ???
Write the following function:
sequenceList :: Monad m => [m a] -> m [a]
sequenceList [] = return []
sequenceList (ma:mas) = do
a <- ma
as <- sequenceList mas
return (a:as)
Write the following function:
mapMList :: Monad m => (a -> m b) -> [a] -> m [b]
mapMList = ???
Write the following function:
mapMList :: Monad m => (a -> m b) -> [a] -> m [b]
mapMList f [] = return []
mapMList f (x:xs) = do
x' <- f x
xs' <- mapMList f xs
return (x:xs)
Basically the same as sequenceList, except we apply f to get a Monad instead of it just already being one.
hcc :: Term -> (Term -> Term) -> Term
hcc t _return = case t of
PrimApp p [] ->
_return (PrimApp p [])
PrimApp p [t1] ->
hcc t1 (\v1 -> _return (PrimApp p [v1]))
PrimApp p [t1, t2] ->
hcc t1 (\v1 -> hcc t2 (\v2 -> _return (PrimApp p [v1, v2])))
Now what if I rewrote it a bit…
hcc :: Term -> (Term -> Term) -> Term
hcc t _return = case t of
PrimApp p [] ->
_return (PrimApp p [])
PrimApp p [t1] ->
hcc t1 (\v1 ->
_return (PrimApp p [v1]))
PrimApp p [t1, t2] ->
hcc t1 (\v1 ->
hcc t2 (\v2 ->
_return (PrimApp p [v1, v2])))
Now what if I factored out the PrimApp p part since it is always the same…
Recall our original problem:
hcc :: Term -> (Term -> Term) -> Term
hcc t _return = case t of
PrimApp p [] ->
_return []
PrimApp p [t1] ->
hcc t1 (\v1 ->
_return [v1])
PrimApp p [t1, t2] ->
hcc t1 (\v1 ->
hcc t2 (\v2 ->
_return [v1, v2]))
Can you see it yet!!??
What if I wasn’t too sure how to write mapMList, I might start to write it like this:
mapMList :: Monad m => (a -> m b) -> [a] -> m [b]
mapMList f xs = case xs of
[] ->
return []
[x1] -> do
x1' <- f x1
return [x1']
[x1, x2] -> do
x1' <- f x1
x2' <- f x2
return [x1', x2']
hcc t _return = case t of
PrimApp p [] ->
_return []
PrimApp p [t1] ->
hcc t1 (\v1 ->
_return [v1])
PrimApp p [t1, t2] ->
hcc t1 (\v1 ->
hcc t2 (\v2 ->
_return [v1, v2]))
mapMList f xs = case xs of
[] ->
return []
[x1] -> do
x1' <- f x1
return [x1']
[x1, x2] -> do
x1' <- f x1
x2' <- f x2
return [x1', x2']
hcc t _return = case t of
PrimApp p [] ->
_return []
PrimApp p [t1] ->
hcc t1 (\v1 ->
_return [v1])
PrimApp p [t1, t2] ->
hcc t1 (\v1 ->
hcc t2 (\v2 ->
_return [v1, v2]))
mapMList f xs = case xs of
[] ->
return []
[x1] ->
f x1 >>= \x1' ->
return [x1']
[x1, x2] ->
f x1 >>= \x1' ->
f x2 >>= \x2' ->
return [x1', x2']
Its just mapM!!!! (with a fmap (PrimApp p))
Are you ready for it!?
-- :: Term -> (Term -> Term) -> Term
hcc :: Term -> Cont Term Term
hcc (PrimApp p ts) = fmap (PrimApp p) (mapM hcc ts)