The list aggregation pattern

The list aggregation pattern

We’ve now seen a number of functions for processing lists. All of these functions share a common structure, which we call a template:

fun <function-name>(<arguments, including lst>) -> <return type>:
  cases (List) lst:
    | empty => <empty case>
    | link(fst, rst) <some processing on fst> <combined with> function-name(rst)
  end
end

Throughout the course, we’ve been informally discussing the sequence of steps we take when writing programs. Here are the steps you should take when writing a list function

  1. Write the name, inputs, input types, and output type for the function.
  2. Write some examples of what the function should produce. The examples should cover all structural cases of the inputs (i.e., empty vs non-empty lists), as well as interesting scenarios within the problem.
  3. Write out the list aggregation template
  4. Implement the function so that it handles the examples correctly

It can often be tempting to skip steps here (do we really need to write out examples?), but we strongly recommend not doing so.

sum-of-squares implementations

Here are two implementations of a function to get the sum of the squares of every number in a list:

import lists as L
import math as M

fun sum-of-squares(lst :: List<Number>) -> Number:
  cases (List) lst:
    | empty => 0
    | link(fst, rst) => (fst * fst) + sum-of-squares(rst)
  end
where:
  sum-of-squares([list: 3, 1, 4]) is (3 * 3) + sum-of-squares([list: 1, 4])
  sum-of-squares([list: 1, 4]) is (1 * 1) + sum-of-squares([list: 4])
  sum-of-squares([list: 4]) is (4 * 4) + sum-of-squares([list: ])
  sum-of-squares([list: ]) is 0
end

fun sum-of-squares2(lst :: List<Number>) -> Number:
  M.sum(L.map(lam(x): x * x end, lst))
end

Which is better? See the lecture capture for discussion details.