The secret nature of lists

We’ve now seen a number of operations on lists:

Today we’ll learn how to write our own list operations.

A sum function

How would we write a function that takes a list of functions and returns its sum? We can start out by writing the skeleton of such a function:

fun my-sum(lst :: List<Number>) -> Number:
  ...
end

We’re calling the function my-sum because Pyret includes a sum function. Can we use any of the list functions we already know about in order to write such a function? For instance, could we use filter?

filter can’t be right–it takes a list, which is what we want, but it returns a boolean. length takes a list and returns a number, but we know it’s not the number we want. map takes a list and returns a list. It seems like none of these functions is going to work!

OK, so we don’t yet know how to write this function. Let’s procrastinate on writing the function body by writing out some examples!

fun my-sum(lst :: List<Number>) -> Number:
  ...
where:
  my-sum([list: 3, 1, 4]) is 3 + 1 + 4
  my-sum([list: 1, 4]) is 1 + 4
  my-sum([list: 4]) is 4
  my-sum([list: ]) is ...
end

What’s the sum of an empty list of numbers? It’s zero.

These examples should start to suggest a pattern (they were chosen for that reason)! We can rewrite them to illustrate this pattern:

fun my-sum(lst :: List<Number>) -> Number:
  ...
where:
  my-sum([list: 3, 1, 4]) is 3 + my-sum([list: 1, 4])
  my-sum([list: 1, 4]) is 1 + my-sum([list: 4])
  my-sum([list: 4]) is 4 + my-sum([list: ])
  my-sum([list: ]) is 0
end

In the top three cases, we’re taking the first element of the list and adding it to my-sum called on the rest of the list. In the last, empty case, we’re doing something a little different–when the list is empty we can’t take the first element, so we’re just going to return 0.

How can we use this in order to complete our my-sum function? In order to do so, we’ll need to learn a bit about the SECRET NATURE OF LISTS.

The secret nature of lists

The notation we’ve been using to write lists (for example, [list: 3, 1, 4]) is actually shorthand for what’s actually going on behind the scenes. Pyret knows about two ways of building a list:

  1. the empty list, called empty
  2. An element added onto the front of a list, called link(fst :: A, rst :: List)

When we write a list using the [list: ...] notation, Pyret translates it into an expression using link and empty. So for instance, [list: 3, 1, 4] is translated into

link(3,
  link(1,
    link(4, empty)))

How can we use this to complete our my-sum function?

The cases expression

Now that we know the secret nature of lists, we can ask Pyret how a given list was built. We do this using the cases expression:

fun my-sum(lst :: List<Number>) -> Number:
  cases (List) lst:
    | empty => ...
    | link(fst, rst) => ...
  end
end

What’s going on here? We can think of cases as being sort of like an if: if the list is empty we should do one thing; if the list is a link, we should do another thing. cases also lets us define names for the components of a link: in this case we’ve named them fst and rst, but we could have called them head and tail, bert and ernie, or anything else (please don’t call them bert and ernie). We can use fst and rst to compute the answer in the link case.

How should we fill out these cases? Our answer comes from the where-block examples we’ve written. Since my-sum([list: ]) is 0, we know that we should return zero in the empty case:1

fun my-sum(lst :: List<Number>) -> Number:
  cases (List) lst:
    | empty => 0
    | link(fst, rst) => ...
  end
end

The link case comes from our examples two! In each case, we took the first element of the list (fst) and added it to the sum of the rest of the list (my-sum(rst)). So we should have:

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

Let’s look at how this function evaluates:

my-sum(link(3, link(1, link(4, empty))))
3 + my-sum(link(1, link(4, empty)))
3 + 1 + my-sum(link(4, empty))
3 + 1 + 4 + my-sum(empty)
3 + 1 + 4 + 0

Footnotes:

1

empty and [list: ] are the same list!