Part 1 — contains
def contains(xs, target):
if xs == []:
return False
elif xs[0] == target:
return True
else:
return contains(xs[1:], target)
Three branches: empty list → definitely not there; head matches → found it; otherwise → the answer
is the same as whether target is in the rest. Trace:
contains([3,7,1], 7)
= contains([7,1], 7) # 3 ≠ 7
= True # 7 == 7 ✓
Short-circuits as soon as it finds a match — it doesn’t scan the rest of the list.
Part 2 — transform
def transform(xs, f):
if xs == []:
return []
else:
return [f(xs[0])] + transform(xs[1:], f)
Trace for transform([1, 2, 3], lambda x: x*x):
transform([1,2,3], sq)
= [sq(1)] + transform([2,3], sq)
= [1] + ([sq(2)] + transform([3], sq))
= [1] + ([4] + ([sq(3)] + transform([], sq)))
= [1] + ([4] + ([9] + []))
= [1, 4, 9]
Functions as arguments — the key move
You just passed a function (f, lambda x: x*x, str.upper) as an argument to another function.
That’s the move. In most languages, a function is just a value you can store in a variable, put in a
list, or pass around. transform doesn’t care what f does — it only cares that f is
callable with one argument. The caller decides the behavior; transform provides the structure
(apply to every element, in order).
This is the seed of higher-order functions — functions that take other functions as arguments or
return them. You’ve already used one: transform itself.
Common pitfalls
- Forgetting the brackets:
f(xs[0])returns a value;[f(xs[0])]wraps it in a list so concatenation with+works. Writingf(xs[0]) + transform(xs[1:], f)tries to add a value to a list — type error. - Wrong base case: returning
Noneinstead of[]breaks concatenation the same way. - Calling
fon the whole list instead of one element:f(xs)instead off(xs[0])— a common slip when skimming.
Why this matters for parallelism (preview)
transform does independent work on each element. Nothing about element 2 depends on what
happened to element 1. That’s the signature of embarrassingly parallel computation — give each
element to a different processor, collect the results. When we build the real map in Stage 2 and
then meet parallel combinators in Stage 7, this independence is exactly the property that makes
safe, lock-free parallelism possible.
Next: filter-by-hand, take, and drop — more list surgery with recursion (Lesson 10).