Higher-Order Functions
Functions that take or return functions
A higher-order function (Def 1.23, slides p.209) is one that takes a function as an argument or returns a function as its result. Functions in SML are first-class values — they can be stored, passed, and returned just like ints or strings.
Examples of taking functions as arguments:
- map applies a function to every element of a list.
- filter keeps only elements satisfying a predicate.
Examples of returning functions:
- fn x => fn y => x + y takes x and returns a function that adds x to its argument. (This is currying.)
- fn k => fn _ => k takes k and returns a constant function.
Composition: the central idea is that we build new behavior by plugging functions together. map f is a function; filter p is a function; map f (filter p xs) chains them. This is the functional analogue of pipes.
Example — define map ourselves:
fun mymap (f, nil) = nil
| mymap (f, h :: t) = (f h) :: mymap (f, t);Test: mymap (fn x => x + 1, [10, 20, 30]) = [11, 21, 31].
- 0.0sHigher-order functions: map
- 1.4sInput row shows cells 1, 2, 3
- 3.1sAn amber function box slides in: fn x implies x times 2
- 4.9sOutput row label appears, empty for now
- 6.0sBox slides over 1, output cell 2 drops in
- 7.7sBox slides over 2, output cell 4 drops in
- 9.4sBox slides over 3, output cell 6 drops in
- 11.0sFunction box moves back and fades out
- 12.0smap applies a function to every element
A function can be written in two equivalent ways:
- Cartesian form: takes all arguments in one tuple.
f(x, y) = x + y. - Cascaded form: returns a function expecting the next argument.
f x = fn y => x + y.
In SML, the cascaded form is the default since functions are curried. The cascaded form f x y is sugar for (f x) y. The cartesian form needs an explicit tuple: f (x, y).
Bracket elision rules (Def 1.26):
1. Application associates left: f a b c = ((f a) b) c.
2. Arrow -> associates right: a -> b -> c -> d = a -> (b -> (c -> d)).
These two rules together mean fn x => fn y => fn z => x + y + z can be written as the cascaded fn x => fn y => fn z => x + y + z and called as f 1 2 3.
The standard higher-order list functions can be re-implemented as my* functions for understanding:
fun myexists p nil = false
| myexists p (h::t) = p h orelse myexists p t;
fun myforall p nil = true
| myforall p (h::t) = p h andalso myforall p t;
fun myfilter p nil = nil
| myfilter p (h::t) = if p h then h :: myfilter p t else myfilter p t;
Patterns:
- myexists p l returns true if at least one element of l satisfies p.
- myforall p l returns true if every element of l satisfies p.
- myfilter p l returns the sublist of elements satisfying p.
Order of recursion matters: all three traverse left-to-right. myexists short-circuits on the first true (good); myforall short-circuits on the first false (good); myfilter cannot short-circuit (must visit all elements to find the ones that pass).
Used in assignments 4.4 and 4.5.
- Assignment 4.4: define divisible d n (uses mod), then nextPrime n, then primes n (uses myexists).
- Assignment 4.5: implement myfilter, myexists, myforall.
Cascaded application. myexists (fn x => x > 5) [3, 7, 2]:
- . Continue.
- . Return true. ✓
SML's standard basis library provides List.nth and List.length. They can be re-implemented:
fun nth 0 (h::_) = h
| nth n (_::t) = nth (n - 1) t
| nth _ nil = raise Subscript;
fun len nil = 0
| len (_::t) = 1 + len t;
List.nth n l returns the -th element of (0-indexed). Raises Subscript if .
List.length is identical to our length (built-in in the playground as length).
Used in assignment 4.8 for permutations: nth i perm accesses the i-th element of a permutation.
Lemma 6.3. A binary relation can be viewed as a function defined by .
Proof. The definition is well-formed: for each it produces a unique subset of . Hence is a (total) function . Conversely, given any function , the relation reconstructs . So relations and functions are equivalent representations.
Use. This view lets us apply functional concepts to relations: composition, identity, inverse.
map (fn x => x * 2) [1, 2, 3] evaluate to?filter (fn x => x > 0) [~1, 2, ~3, 4] evaluate to?fn k => (fn _ => k) higher-order?map?fun f x y = x + y has type:myexists with an all-false predicate returns:List.nth 2 [10, 20, 30, 40] returns: