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