GIML: Lesson Six

Common recursive patterns

map

You will have noticed that certain patterns crop up in recursive functions. The following functions double and increment every item in a list respectively: fun doublist nil = nil | doublist(h::t) = 2*h :: (doublist t); fun inclist nil = nil | inclist(h::t) = (h+1) :: (inclist t); Typical executions: doublist [1,2,3,4] = [2,4,6,8] inclist [1,2,3,4] = [2,3,4,5] Plainly we can abstract out of this a function which applies a function over a list. This is map: fun map f nil = nil | map f (h::t) = (f h)::(map f t); Alternative definitions for doublist and inclist are val doublist = map (fn x=>2*x); val inclist = map (fn x=> x+1);

reduce

reduce takes a binary function (a function with two inputs) a base value and a list. It applys the function repeatedly down the list. For example
reduce f b [i1, i2, i3] = f(i1, f(i2, f(i3, b)))
Consider the connection between the functions sum and flatten (the function flatten turns a list of lists into a simple list) fun sum nil = 0 | sum(h::t) = h + sum t; fun flatten nil = nil | flatten (h::t) = h @ flatten t; Typical executions: sum [10, 20, 30] = 60 flatten [[1,2],[3,4],[5,6,7]] = [1,2,3,4,5,6,7] This second pattern is the reduce pattern - we have a base value for the nil list, for a cons node we apply a binary (two input) function f which is applied to the head and the recursive call: fun reduce f b nil = b | reduce f b (h::t) = f(h,reduce f b t); We can now redefine sum and flatten: val sum = reduce (fn(a,b)=>a+b) 0; val flatten = reduce (fn(a,b)=>a@b) nil; In fact we can do even better, ML allows use to convert infix functions such as + and @ into the prefix form required using the keyword op. reduce (op +) 0 [1,2,3,4]; reduce (op @) nil [[1,2],[3,4],[5,6,7]];

fold

The predefined function fold is the same as reduce - but the arguements are in a different order. We might have defined reduce as..
fun reduce f b l = fold f l b; 

zip

zip can be used to apply a binary function (one with two inputs) to the corresponding elements of two lists. For example
zip (op +) [1,2,3] [2,4,6] = [3,6,9]
zip is defined as follows
fun zip f nil nil = nil
|   zip f (h::t) (i::u) = f(h,i)::zip f t u;

filter

filter takes a predicate (a function which returns true or false) and a list. It returns the list with only those items for which the predicate s true.

Suppose the function even : int -> bool has been defined as

fun even n = (n mod 2 = 0);
then applying filter even over the list [1,2,3,4,5,6] would return only the even values.
filter even [1,2,3,4,5,6] = [2,4,6]