Even more recursion
Infinite executions?
What will happen if we execute this function?
fun any-below-10-wrong(lst :: List<Number>) -> Boolean: cases (List) lst: | empty => false | link(fst, rst) => (fst < 10) or any-below-10(lst) end end
We can try and see how it evaluates, by substitution as usual:
any-below-10-wrong([list: 11, 4, 5]) (11 < 10) or any-below-10-wrong([list: 11, 4, 5]) (11 < 10) or (11 < 10) or any-below-10-wrong([list: 11, 4, 5])
We could keep going with this, but it’s not going to end up anywhere useful!
How might you tell if a program is going to end up spinning forever like
any-below-10-wrong
? How might you avoid it?
Notice that the function doesn’t always spin forever:
any-below-10-wrong([list: 3, 1, 4])
Building lists
We’ve now seen functions that process lists and return booleans, numbers, and images. How about functions that process lists and return lists?
add-1-all
and map
Let’s write a function that adds 1 to every number in a list:
fun add-1-all(lst :: List<Number>) -> List<Number>: doc: "adds one to every element of the list" ... end
By now you might be able to anticipate how we start: by writing where
examples. Let’s write them in terms of empty
and link
this time.
fun add-1-all(lst :: List<Number>) -> List<Number>: doc: "adds one to every element of the list" ... where: add-1-all(link(3, link(1, link(4, empty)))) is link(4, link(2, link(5,empty))) add-1-all(link(1, link(4, empty))) is link(2, link(5, empty)) add-1-all(link(4, empty)) is link(5, empty) add-1-all(empty) is empty end
We can rewrite these:
fun add-1-all(lst :: List<Number>) -> List<Number>: doc: "adds one to every element of the list" ... where: add-1-all(link(3, link(1, link(4, empty)))) is link(4, add-1-all(link(1, link(4, empty)))) add-1-all(link(1, link(4, empty))) is link(2, add-1-all(link(4, empty))) add-1-all(link(4, empty)) is link(5, add-1-all(empty)) add-1-all(empty) is empty end
So how will we fill in the body of this function? How about:
fun add-1-all(lst :: List<Number>) -> List<Number>: doc: "adds one to every element of the list" cases (List) lst: | empty => empty | link(fst, rst) => link(fst + 1, add-1-all(rst)) where: add-1-all(link(3, link(1, link(4, empty)))) is link(4, add-1-all(link(1, link(4, empty)))) add-1-all(link(1, link(4, empty))) is link(2, add-1-all(link(4, empty))) add-1-all(link(4, empty)) is link(5, add-1-all(empty)) add-1-all(empty) is empty end
The map
function is identical, except that it takes a function and applies it
instead of adding one:
fun my-map(f : Function, lst :: List) -> List: doc: "calls f on every member of the list" cases (List) lst: | empty => empty | link(fst, rst) => link(f(fst), my-map(rst)) end
Something that often trips people up when writing functions like this is the
difference between link(x, y)
and [list: x, y]
. Both are lists–how many
elements does each list have?
remove-fours
and filter
As a last exercise, let’s walk through another list processing function:
remove-fours
, which removes all fours from a list of numbers. Personally I
think four is a fine number, but not everyone agrees!
fun remove-fours(lst :: List<Number>) -> List<Number>: doc: "remove my least favorite number from a list" cases (List) lst: | empty => empty | link(x, rst) => if x == 4: remove-fours(rst) else: link(x, remove-fours(rst)) end end where: remove-fours([list: 3, 4, 5]) is link(3, remove-fours([list: 4, 5])) remove-fours([list: 4, 5]) is remove-fours([list: 5]) remove-fours([list: 5]) is link(5, remove-fours([list: ])) remove-fours([list: ]) is [list: ] end