More recursive functions

Recursion

Last time we wrote a few functions that call themselves--my-sum, all-below-10, and my-all. These functions all use recursion.

Recursion
When a function or computation calls itself

Recursion can be tricky to understand, but it’s extremely useful. We’ve talked a lot about writing solutions that use the structure of the problem. Recursion lets us do this for many problems–any time the problem is structured such that the solution on larger inputs can be built from the solution on smaller inputs, recursion is appropriate.

Infinite executions?

What will happen if we execute this function?

fun uh-oh(x :: Number) -> Number:
  doc: "performs a very crucial computation"
  x + uh-oh(x + 1)
end

We can try and see how it evaluates, by substitution as usual:

uh-oh(1)
1 + uh-oh(2)
1 + 2 + uh-oh(3)

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 uh-oh? How might you avoid it?

The any function

We had some drill questions about any. Let’s take a look at the function body:

fun my-any(f :: Function, lst :: List) -> Boolean:
  doc: "returns true if f returns true on any element of the list; false otherwise"
  cases (List) lst:
    | empty => false
    | link(fst, rst) => f(fst) or my-any(f, lst)
  end
end

Why does it return false? Why does it use or instead of and?

Stripey snakes

Way back in the beginning of the semester, we worked on creating images of snakes. We wrote a function like this one to create a particular pattern of stripes:

fun three-stripe-pattern(left :: String, middle :: String, right :: String) -> Image:
  doc: "Creates a snake pattern with three stripes"
  frame(beside(rectangle(30, 100, "solid", left),
      beside(rectangle(30, 100, "solid", middle),
        rectangle(30, 100, "solid", right))))
end

This creates a pattern with three stripes beside each other. What if we wanted to create a pattern with any number of stripes?

fun multi-stripe-pattern(colors :: List<String>) -> Image:
  doc: "creates a striped pattern with the provided colors"
  ...
end

We don’t normally write where examples for images, but let’s do one for this function since it will help us develop the function body. What should multi-stripe-pattern([list: "red", "brown"]) return? Maybe something like

beside(rectangle(30, 100, "solid", "red"),
  beside(rectangle(30, 100, "solid", "brown")))

So what should multi-stripe-pattern([list: ]) return? Can we have an “empty” image? Does this change our answer for multi-stripe-pattern([list: "red", "brown"])?

We could do this in a couple of different ways. We could require a non-empty list, on the grounds that making an empty striped pattern doesn’t make sense. We’ll see how to do that later on. Our other option is to allow calls with an empty list, and return empty-image in that case. empty-image is just what it sounds like–an image with no dimensions and no content, designed to be used in cases like this. If we go with this option, then we’ll have

fun multi-stripe-pattern(colors :: List<String>) -> Image:
  ...
where:
  multi-stripe-pattern([list: "red", "brown"]) is
  beside(rectangle(30, 100, "solid", "red"),
    beside(rectangle(30, 100, "solid", "brown"),
      empty-image))

  multi-stripe-pattern([list: "brown"]) is
  beside(rectangle(30, 100, "solid", "brown"),
    empty-image)

  multi-stripe-pattern([list: ]) is empty-image

end

So, how will we fill in the body? We’ve done a few of these list processing functions, so we know how to start:

fun multi-stripe-pattern(colors :: List<String>) -> Image:
  cases (List) colors:
    | empty => ...
    | link(fst, rst) => ...
  end
end

We know what to put in the empty case: empty-image. We should have some idea of what to do in the link case, too: we’ll need to put a rectangle of some color beside something else.

fun multi-stripe-pattern(colors :: List<String>) -> Image:
  cases (List) colors:
    | empty => empty-image
    | link(fst, rst) => beside(rectangle(30, 100, "solid", fst),
        multi-stripe-pattern(rst))
  end
end

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