17. Recursion
Lessons 1 to 16 are the graded course. From here on the lessons go a little deeper into corners of the language you can reach for once the basics are comfortable.
A function can call itself. In Pyfun a name is in scope inside its own body, the same way a Python
def can refer to itself, so recursion just works with no rec keyword to turn it on:
let fact n =
if n == 0 then 1
else n * fact (n - 1)
print (fact 5)
120
fact refers to fact in its own else branch, and each call shrinks n toward the n == 0 base
case that stops the descent. Two functions can lean on each other the same way. Mutual recursion
needs no special form either, and the order you write them in does not matter, because both names
are in scope across the pair:
let isEven n = if n == 0 then true else isOdd (n - 1)
let isOdd n = if n == 0 then false else isEven (n - 1)
print (isEven 10)
print (isOdd 7)
True
True
A recursive Pyfun function compiles to a plain Python function, so it shares Python’s call stack and
its recursion limit. A recursion thousands of levels deep will overflow that stack, the same as it
would in hand-written Python. For walking a long list or summing a large collection, reach for
List.fold and the other combinators from lesson 7, which loop underneath and stay flat. Recursion
earns its keep on tree-shaped data, where the depth follows the structure rather than the size of
the input.
An arithmetic expression is a natural tree: a number is a leaf, and an addition or a multiplication
holds two smaller expressions. The lesson 5 material models it as an ADT, and eval walks it by
recursing into each branch:
type Expr =
| Num int
| Add Expr Expr
| Mul Expr Expr
let eval e =
match e:
case Num n: n
case Add l r: eval l + eval r
case Mul l r: eval l * eval r
let tree = Add (Num 1) (Mul (Num 2) (Num 3))
print (eval tree)
7
Each case handles one shape, and the Add and Mul arms call eval on their sub-expressions, so
the recursion bottoms out at the Num leaves. The depth of the calls matches the depth of the tree,
which for a balanced expression stays small even when the tree holds many nodes.
Exercise
Tree holds an int at every leaf and branches in two. total should add up every leaf. The
Leaf case is done, but the Branch case is a hole. Run pyfun check to see the type it expects,
then combine the totals of the two sub-trees.
type Tree =
| Leaf int
| Branch Tree Tree
let total t =
match t:
case Leaf n: n
case Branch l r: ?
let sample = Branch (Leaf 1) (Branch (Leaf 2) (Leaf 3))
print (total sample)
The checker reports:
note: hole `?` has type `int` — or: total ?, List.sum ?, String.len ?, ceil ?
--> 8:22
|
8 | case Branch l r: ?
| ^
1 unfilled hole
Expected output:
6
Show solution
type Tree =
| Leaf int
| Branch Tree Tree
let total t =
match t:
case Leaf n: n
case Branch l r: total l + total r
let sample = Branch (Leaf 1) (Branch (Leaf 2) (Leaf 3))
print (total sample)
The Branch arm calls total on the left and right sub-trees and adds the two results, so the
recursion follows the branches down to the leaves and sums them on the way back up. The hole even
suggests total ? under or:, pointing at the recursive call itself.