Chapter 4Standard MLslides.en.pdf:227

Recursion

Functions that call themselves

Def 1.37Tail call
Concept

A function is recursive when it calls itself (Def 1.17). Recursion is how SML expresses loops and iteration.

Every well-formed recursive function has two parts:
1. Base case(s) — handle the smallest input directly, without recursion.
2. Recursive case(s) — handle larger inputs by calling the function on a smaller sub-piece.

Example — length of a list:

fun length nil = 0 | length (_ :: t) = 1 + length t;

Base case: empty list has length 0. Recursive case: a non-empty list has length 1 plus the length of its tail.

Example — sum of a list:

fun sum nil = 0 | sum (h :: t) = h + sum t;

Example — factorial (n!):

fun fact 0 = 1 | fact n = n * fact (n - 1);

Tail calls (Def 1.37, slides p.227): when the recursive call is the LAST thing the function does, modern compilers optimize it into a loop. So tail-recursive functions are as efficient as for-loops. Compare:
fun sum (h :: t) = h + sum t; — NOT tail-recursive (the + happens after the recursive call returns).
fun sum_tr nil acc = acc | sum_tr (h :: t) acc = sum_tr t (acc + h); — tail-recursive with an accumulator.

Animation — sml recursion
Transcript — click a line to jump9 cues
  1. 0.0sRecursion in SML: length [1, 2, 3]
  2. 1.4sRoot call: length [1, 2, 3]
  3. 2.8sUnfolds to 1 + length [2, 3]
  4. 3.9sThen 1 + 1 + length [3]
  5. 5.0sThen 1 + 1 + 1 + length []
  6. 6.1sBase case length [] highlighted in amber
  7. 7.3sTree fades, final result 1 + 1 + 1 + 0 = 3
  8. 9.3sEach recursive call passes a shorter list
  9. 10.5sTermination is guaranteed by the shrinking input
Theorem: Bijective iff invertible

Theorem. f:ABf: A \to B is bijective if and only if it has an inverse f1:BAf^{-1}: B \to A with f1(f(a))=af^{-1}(f(a)) = a and f(f1(b))=bf(f^{-1}(b)) = b for all aA,bBa \in A, b \in B.

Proof. (\Rightarrow) Assume ff is bijective.
- Injectivity: for each bBb \in B, there is at most one aa with f(a)=bf(a) = b.
- Surjectivity: for each bBb \in B, there is at least one aa with f(a)=bf(a) = b.
- Combined: there is exactly one aa per bb. Define f1(b)f^{-1}(b) to be that aa.
- Then f1(f(a))=af^{-1}(f(a)) = a (by definition) and f(f1(b))=bf(f^{-1}(b)) = b (also by definition). ✓

(\Leftarrow) Assume f1f^{-1} exists with both composition identities.
- Injectivity: if f(a1)=f(a2)=bf(a_1) = f(a_2) = b, then a1=f1(b)=a2a_1 = f^{-1}(b) = a_2. ✓
- Surjectivity: for any bBb \in B, let a=f1(b)a = f^{-1}(b). Then f(a)=bf(a) = b. ✓

So ff is bijective. \square

Mathematical fold

The fold (or reduce) operator collapses a list into a single value by repeatedly combining adjacent elements. For binary operation \oplus with identity ee:
- fold([x1,x2,,xn])=(((ex1)x2)xn)\text{fold}_\oplus([x_1, x_2, \ldots, x_n]) = (\cdots((e \oplus x_1) \oplus x_2) \oplus \cdots \oplus x_n)

Examples:
- fold+([1,2,3,4])=10\text{fold}_+([1,2,3,4]) = 10 (sum).
- fold×([1,2,3,4])=24\text{fold}_\times([1,2,3,4]) = 24 (product).
- fold([T,T,F])=F\text{fold}_\wedge([T,T,F]) = F (AND).
- fold([F,T,F])=T\text{fold}_\vee([F,T,F]) = T (OR).

In SML this is foldl and foldr (with the difference being association order).

Worked example
Step 0 of 2
Practice — score 100% to advance
Multiple choice
Q1
Per Def 1.17, a function is recursive iff…
Q2
What are the two parts every well-formed recursive function should have?
Q3
What does fun fact 0 = 1 | fact n = n * fact (n-1) compute?
Q4
What is a tail call (Def 1.37)?
Q5
Why are tail-recursive functions important?
Q6
Is fun sum (h :: t) = h + sum t tail-recursive?
Q7
If a recursive function has no base case, what happens?
Q8
If ff is bijective, then f1f^{-1} is:
Fill in the blank
Q1
fold_+ applied to [1,2,3,4] returns ___.
Loading…