Lecture 13 Tail Recursion

Joseph Haugh

University of New Mexico

Review

  • What is the type of map?
map :: (a -> b) -> [a] -> [b]

Review

  • What is the type of filter?
filter :: (a -> Bool) -> [a] -> [a]

Review

  • What is the type of foldr?
foldr :: (a -> b -> b) -> b -> [a] -> b

Review

  • What is the type of map (take 10)?
map (take 10) :: [[a]] -> [[a]]

Review

  • What is the type of foldr (rest -> x : filter (/= x) rest)
foldr (\x rest -> x : filter (/= x) rest) :: [a] -> [a] -> [a]

Could this have been [b] -> [a] -> [b]?

Two Ways to Write the Same Function

Remember the factorial function?

factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

Two Ways to Write the Same Function

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

Two Ways to Write the Same Function

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

Two Ways to Write the Same Function

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

Two Ways to Write the Same Function

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?

Call Stack

factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

factorial 3











Call Stack

factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

factorial 3
| { applying factorial, push stack }
n * factorial (n-1)









======
n = 3|
======

Call Stack

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|
======

Call Stack

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|
======

Call Stack

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|
======

Call Stack

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|
======

Call Stack

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|
======

Call Stack

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|
======

Call Stack

factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

n * (2)
| { applying * with n = 3 }
6
| { pop stack }








======
n = 3|
======

Call Stack

factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

n * (2)
| { applying * with n = 3 }
6
| { pop stack }










Call 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

Another Way

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

Tail recursion doesn’t need to push stack frames because the calculations can all be done before the recursive call

Tail Recursion Example: factorial

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

Tail Recursion Example: factorial

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

Tail Recursion Example: factorial

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

Tail Recursion Example: length

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

Tail Recursion Example: and

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

Tail Recursion

Can you spot the pattern?

Add an accumulator to the function and return it in the base case

Each recursive call updates the accumulator

Exercise: Tail Recursive sum

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 ... ... = ...

Exercise: Tail Recursive sum

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)

Exercise: Tail Recursive reverse

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 ... ... = ...

Exercise: Tail Recursive reverse

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)

Exercise Challenge: Tail Recursive fibonacci

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 ... ... ... = ...

Exercise Challenge: Tail Recursive fibonacci

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)

Tail Call Optimization

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