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