Fun with Functions
In the previous section we learned about functions. I said we can write our structural recursions over natural numbers using the method fold
below, which accepts a function as a parameter.
def fold(count: Int, build: (Int, Image) => Image): Image =
count match {
case 0 => Image.empty
case n => build(n, fold(count - 1, build))
}
Let's see if that is really the case, and get a bit more practice using functions.
Below is an example of a row of boxes, where the color changes from each box to the next. We've already written this as a method. Now I want you to rewrite it using fold
and a function you create.
Here's how I wrote it.
val gradientBoxes: (Int, Image) => Image =
(count, image) =>
Image
.square(50)
.fillColor(Color.royalBlue.spin(10.degrees * count))
.noStroke
.beside(image)
fold(5, gradientBoxes)
If you struggled to come up with this look at the version written as a method.
def gradientBoxes(count: Int): Image =
count match {
case 0 => Image.empty
case n =>
Image
.square(50)
.fillColor(Color.royalBlue.spin(10.degrees * count))
.noStroke
.beside(gradientBoxes(count-1))
}
All we're doing is extracting the problem specific part in the recursive case
case n =>
Image
.square(50)
.fillColor(Color.royalBlue.spin(10.degrees * count))
.noStroke
.beside(gradientBoxes(count-1))
and turning it into a function.
Here's a variation on that idea, which changes size as well as changing color. Write this using fold
and a function of your own construction.
Here's what I wrote. It's the same idea as the previous example, with a small modification to change the size of the element as well as the color.
val growingCircles: (Int, Image) => Image =
(count, image) =>
Image
.circle(20 * count)
.fillColor(Color.crimson.spin(10.degrees * count))
.noStroke
.beside(image)
fold(5, growingCircles)
Let's try a fractal. Below is the Sierpinski triangle. Can you write this using fold
? If not, why not? Can you change fold
so you can write it using fold
?
The way the question is worded is a strong hint that we cannot write the Sierpinski triangle using fold
as we have currently written it. The reason is that fold
, as currently written, always uses Image.empty
as the base case. For the Sierpinski triangle we need a different base case. The solution is to add another parameter to fold
that allows us to change the base case.
A More General Fold
In the previous section we saw there are some kinds of Images
we cannot create using fold
. A more general fold will allow us to also vary the base case as well as the recursive case. This is simply another parameter to the method.
def fold(count: Int, base: Image, build: (Int, Image) => Image): Image =
count match {
case 0 => base
case n => build(n, fold(count - 1, base, build))
}
Using this version of fold we can create the Sierpinski triangle.
val sierpinski: (Int, Image) => Image =
(count, image) => image.above(image.beside(image))
fold(
5,
Image.equilateralTriangle(10).strokeColor(Color.hotpink),
sierpinski
)
To make a fully general fold we'd need to be able to change the result type. This requires generic types, which we will learn about in a later section.