More recursive functions

my-sum and the program directory

We talked about how the my-sum function we wrote last class executes. See lecture capture for details.

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.

The any-below-10 function

Let’s work on another function over lists of numbers. any-below-10 should return true if any member of the list is less than 10, and false otherwise. We’ve seen one way of writing it:

fun any-below-10(lst :: List<Number>) -> Boolean:
  any(lam(x): x < 10 end, lst)
end

This will work fine, but let’s try to write it with cases. Once we’ve done that, we’ll be able to see how all is actually implemented!

As we did with my-sum, we’ll start with some tests:

fun any-below-10(lst :: List<Number>) -> Boolean:
  ...
where:
  any-below-10([list: 3, 1, 4]) is (3 < 10) or (1 < 10) or (4 < 10)
  any-below-10([list: 1, 4]) is (1 < 10) or (4 < 10)
  any-below-10([list: 4]) is (4 < 10)
  any-below-10([list: ]) is ...
end

What should go in that last case? Are any of the numbers in an empty list below 10? No! So, let’s put false.

We can rewrite these tests again:

fun any-below-10(lst :: List<Number>) -> Boolean:
  ...
where:
  any-below-10([list: 3, 1, 4]) is (3 < 10) or any-below-10([list: 1, 4])
  any-below-10([list: 1, 4]) is (1 < 10) or any-below-10([list: 4])
  any-below-10([list: 4]) is (4 < 10) or any-below-10([list: ])
  any-below-10([list: ]) is false
end

This suggests what our function body should look like:

fun any-below-10(lst :: List<Number>) -> Boolean:
  cases (List) lst:
    | empty => false
    | link(fst, rst) => (fst < 10) or any-below-10(rst)
  end
where:
  any-below-10([list: 3, 1, 4]) is (3 < 10) or any-below-10([list: 1, 4])
  any-below-10([list: 1, 4]) is (1 < 10) or any-below-10([list: 4])
  any-below-10([list: 4]) is (4 < 10) or any-below-10([list: ])
  any-below-10([list: ]) is false
end

Now that we’ve seen how this works, we can see how any is implemented: it’s exactly the same, except that it applies some particular function instead of just checking if numbers are less than 10:

fun my-any(f, lst :: List) -> Boolean:
  cases (List) lst:
    | empty => false
    | link(fst, rst) => f(fst) or my-any(f, rst)
  end
end

Striped flags

We did a lecture activity about a function to create flags with horizontal stripes. See the lecture capture for details! We ended up with this code:

include image
fun striped-flag(colors :: List<String>) -> Image:
  doc: "produce a flag with horizontal stripes"
  cases (List) colors:
    | empty => empty-image
    | link(color, rest) =>
      stripe = rectangle(120, 30, "solid", color)
      above(stripe, striped-flag(rest))
  end
end