Joseph Haugh
University of New Mexico
map :: (a -> b) -> [a] -> [b]
filter :: (a -> Bool) -> [a] -> [a]
foldr :: (a -> b -> b) -> b -> [a] -> b
map (take 10) :: [[a]] -> [[a]]
foldr (\x rest -> x : filter (/= x) rest) :: [a] -> [a] -> [a]
Could this have been [b] -> [a] -> [b]?
Remember the factorial function?
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
factorial 3
| { applying factorial }
3 * factorial 2
| { applying factorial }
3 * (2 * factorial 1)
| { applying factorial }
3 * (2 * (1 * factorial 0))
| { applying factorial }
3 * (2 * (1 * 1))
| { applying * }
6
length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs
length [1,2,3]
| { applying length }
1 + length [2,3]
| { applying length }
1 + (1 + length [3])
| { applying length }
1 + (1 + (1 + length []))
| { applying length }
1 + (1 + (1 + 0))
| { applying + }
3
and :: [Bool] -> Bool
and [] = True
and (b:bs) = b && and bs
and [True, False, True]
| { applying and }
True && and [False, True]
| { applying and }
True && (False && and [True])
| { applying and }
True && (False && (True && and []))
| { applying and }
True && (False && (True && True))
| { applying && }
False
What do all these functions have in common?
They build their result by combining a value from the list with the recursive call to the tail
What does this look like in the call stack?
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
factorial 3
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
factorial 3
| { applying factorial, push stack }
n * factorial (n-1)
======
n = 3|
======
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
n * factorial (n-1)
| { applying factorial, push stack }
n * (n * factorial n-1)
======
n = 2|
======
======
n = 3|
======
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
n * (n * factorial n-1)
| { applying factorial, push stack }
n * (n * (n * factorial (n-1)))
======
n = 1|
======
======
n = 2|
======
======
n = 3|
======
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
n * (n * (n * factorial (n-1)))
| { applying factorial, push stack }
n * (n * (n * (1)))
======
n = 0|
======
======
n = 1|
======
======
n = 2|
======
======
n = 3|
======
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
n * (n * (n * (1)))
| { pop stack }
======
n = 0|
======
======
n = 1|
======
======
n = 2|
======
======
n = 3|
======
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
n * (n * (n * (1)))
| { applying inner * with n = 1 }
n * (n * (1))
| { pop stack }
======
n = 1|
======
======
n = 2|
======
======
n = 3|
======
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
n * (n * (1))
| { applying inner * with n = 2 }
n * (2)
| { pop stack }
======
n = 2|
======
======
n = 3|
======
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
n * (2)
| { applying * with n = 3 }
6
| { pop stack }
======
n = 3|
======
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
n * (2)
| { applying * with n = 3 }
6
| { pop stack }
Each recursive call pushes a stack frame to the stack
This is where the infamous stack overflow error comes from
Can we get one in Haskell?
> factorial 123456789
*** Exception: stack overflow
Can we alleviate this problem?
Of course! We can use tail recursion!
Tail recursion is a special kind of recursion where we do not need to push stack frames for each recursive call
Tail recursion doesn’t need to push stack frames because the calculations can all be done before the recursive call
Let’s rewrite factorial to be tail recursive
factorial :: Integer -> Integer
factorial n = go n 1
where
go 0 acc = acc
go n acc = go (n - 1) (n * acc)
Let’s try it with the big number:
> factorial 123456789
BIG#
We won’t get a stack overflow error
factorial :: Integer -> Integer
factorial n = go n 1
where
go 0 acc = acc
go n acc = go (n - 1) (n * acc)
Notice that before the recursive call we can already calculate an intermediate result:
(n - 1) and (n * acc)
Whereas before we need to know that result of the recursion before completing the calculation
factorial n = go n 1
where
go 0 acc = acc
go n acc = go (n - 1) (n * acc)
factorial 3
| { applying factorial }
go 3 1
| { applying go }
go 2 3
| { applying go }
go 1 6
| { applying go }
go 0 6
| { applying go }
6
length :: [a] -> Int
length xs = go xs 0
where
go [] acc = acc
go (x:xs) acc = go xs (acc + 1)
length [1,2,3]
| { applying length }
go [1,2,3] 0
| { applying go }
go [2,3] 1
| { applying go }
go [3] 2
| { applying go }
go [] 3
| { applying go }
3
and :: [Bool] -> Bool
and xs = go xs True
where
go [] acc = acc
go (x:xs) acc = go xs (x && acc)
and [True, False, True]
| { applying and }
go [True, False, True] True
| { applying go }
go [False, True] True
| { applying go }
go [True] False
| { applying go }
go [] False
| { applying go }
False
Can you spot the pattern?
Add an accumulator to the function and return it in the base case
Each recursive call updates the accumulator
Write a tail recursive version of the sum function
length :: [a] -> Int
length xs = go xs 0
where
go [] acc = acc
go (x:xs) acc = go xs (1 + acc)
sum :: [Int] -> Int
sum xs = go ... ...
where
go ... ... = ...
Write a tail recursive version of the sum function
length :: [a] -> Int
length xs = go xs 0
where
go [] acc = acc
go (x:xs) acc = go xs (1 + acc)
sum :: [Int] -> Int
sum xs = go xs 0
where
go [] acc = acc
go (x:xs) acc = go xs (x + acc)
Write a tail recursive version of the reverse function
reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
reverseTR :: [a] -> [a]
reverseTR xs = go ... ...
where
go ... ... = ...
Write a tail recursive version of the reverse function
reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
reverseTR :: [a] -> [a]
reverseTR xs = go xs []
where
go [] acc = acc
go (x:xs) acc = go xs (x : acc)
Write a tail recursive version of the fibonacci function
fib :: Integer -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
fibTR :: Integer -> Integer
fibTR n = go ... ... ...
where
go ... ... ... = ...
Write a tail recursive version of the fibonacci function
fib :: Integer -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
fibTR :: Integer -> Integer
fibTR n = go n 0 1
where
go 0 a b = a
go n a b = go (n - 1) b (a + b)
This is only possible because the Haskell compiler is smart enough to optimize tail recursive functions and avoid pushing stack frames
Many languages do not have this feature so be wary