The secret nature of lists
We’ve now seen a number of operations on lists:
memberdistinctappendlengthfilterall
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:
- the empty list, called
empty - 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:
empty and [list: ] are the same list!