Recursion is…
Building solutions for big problems from solutions for sub-problems
- Base case: what is the simplest version of this problem and how do I solve it?
- Inductive strategy: how do I break down this problem into sub-problems?
- Inductive case: how do I solve the problem given the solutions for subproblems?
Why use Recursion?
Often far simpler and cleaner than loops
- But not always…
Structure often forced by recursive data
Forces you to factor code into reusable units (recursive functions)
Why not use Recursion?
Slow
Can cause stack overflow
Example: factorial
fac :: Int -> Int
fac n| n <= 1 = 1
| otherwise = n * fac (n - 1)
Lets see how fac 4
is evaluated:
<fac 4>
==> <4 * <fac 3>> -- recursively call `fact 3`
==> <4 * <3 * <fac 2>>> -- recursively call `fact 2`
==> <4 * <3 * <2 * <fac 1>>>> -- recursively call `fact 1`
==> <4 * <3 * <2 * 1>>> -- multiply 2 to result
==> <4 * <3 * 2>> -- multiply 3 to result
==> <4 * 6> -- multiply 4 to result
==> 24
Each function call <>
allocates a frame on the call stack
- expensive
- the stack has a finite size
Can we do recursion without allocating stack frames?
Tail Recursion
No computations allowed on recursively returned value
- i.e. value returned by the recursive call == value returned by function
QUIZ: Is this function tail recursive?
fac :: Int -> Int
fac n| n <= 1 = 1
| otherwise = n * fac (n - 1)
A. Yes
B. No
Answer: B
Tail recursive factorial
Let’s write a tail-recursive factorial!
facTR :: Int -> Int
= loop 1 n
facTR n where
loop :: Int -> Int -> Int
loop acc n| n <= 1 = acc
| otherwise = loop (acc * n) (n - 1)
Lets see how facTR
is evaluated:
<facTR 4>
==> <<loop 1 4>> -- call loop 1 4
==> <<<loop 4 3>>> -- rec call loop 4 3
==> <<<<loop 12 2>>>> -- rec call loop 12 2
==> <<<<<loop 24 1>>>>> -- rec call loop 24 1
==> 24 -- return result 24!
Each recursive call directly returns the result
without further computation
no need to remember what to do next!
no need to store the “empty” stack frames!
Why care about Tail Recursion?
Because the compiler can transform it into a fast loop
= loop 1 n
facTR n where
loop acc n| n <= 1 = acc
| otherwise = loop (acc * n) (n - 1)
function facTR(n){
var acc = 1;
while (true) {
if (n <= 1) { return acc ; }
else { acc = acc * n; n = n - 1; }
} }
Tail recursive calls can be optimized as a loop
- no stack frames needed!
Part of the language specification of most functional languages
- compiler guarantees to optimize tail calls
That’s all folks!