Auxiliary Parameters
We've seen how to use structural recursion over the natural numbers to write a number of interesting programs. In this section we're going to learn how auxillary parameters allow us to write more complex programs. An auxiliary parameter is just an additional parameter to our method that allows us to pass extra information down the recursive call.
For example, imagine creating the picture below which shows a line of boxes that grow in size as we move along the line.
How can we create this image?
We know it has to be a structural recursion over the natural numbers, so we can immediately write down the skeleton
def growingBoxes(count: Int): Image =
count match {
case 0 => base
case n => unit add growingBoxes(n-1)
}
Using what we learned working with boxes
earlier we can go a bit further and write down
def growingBoxes(count: Int): Image =
count match {
case 0 => Image.empty
case n => Image.square(???).beside(growingBoxes(n-1))
}
The challenge becomes how to make the box grow in size as we move to the right.
There are two ways to do this.
The tricky way is to switch the order in the recursive case and make the size of the box a function of n
.
Here's the code.
def growingBoxes(count: Int): Image =
count match {
case 0 => Image.empty
case n => growingBoxes(n-1).beside(Image.square(n*10))
}
Spend some time figuring out why this works before moving on to the solution using an auxiliary parameter.
Alternatively we can simply add another parameter to growingBoxes
that tells us how big the current box should be.
When we recurse we change this size.
Here's the code.
def growingBoxes(count: Int, size: Int): Image =
count match {
case 0 => Image.empty
case n =>
Image
.square(size)
.beside(growingBoxes(n-1, size + 10))
}
The auxiliary parameter method has two advantages: we only have to think about what changes from one recursion to the next (in this case, the box gets larger), and it allows the caller to change this parameter (for example, making the starting box larger or smaller).
Now we've seen the auxiliary parameter method let's practice using it.
Exercise: Gradient Boxes
In this exercise we're going to draw a picture like that shown below. We already know how to draw a line of boxes. The challenge in this exercise is to make the color change at each step.
Hint: you can spin
the fill color at each recursion.
There are two ways to implement a solution.
The auxiliary parameter method is to add an extra parameter to gradientBoxes
and pass the Color
through the structural recursion.
def gradientBoxes(n: Int, color: Color): Image =
n match {
case 0 => Image.empty
case n =>
aBox
.fillColor(color)
.beside(gradientBoxes(n - 1, color.spin(15.degrees)))
}
We could also make the fill color a function of n
, as we demonstrated with the box size in growingBoxes
above.
def gradientBoxes(n: Int): Image =
n match {
case 0 => Image.empty
case n =>
aBox
.fillColor(Color.royalBlue.spin((15 * n).degrees))
.beside(gradientBoxes(n - 1))
}
Exercise: Concentric Circles
Now let's try a variation on the theme, drawing concentric circles as in the picture below. Here we are changing the size rather than the color of the image at each step. Otherwise the pattern stays the same. Have a go at implementing it.
This is almost identical to growingBoxes
.
def concentricCircles(count: Int, size: Int): Image =
count match {
case 0 => Image.empty
case n =>
Image
.circle(size)
.on(concentricCircles(n-1, size + 5))
}
Exercise: Once More
Now let's combine both techniques to change size and color on each step, giving results like those shown below. Experiment until you find something you like.
Here's our solution, where we've tried to break the problem into reusable parts and reduce the amount of repeated code. We still have a lot of repetition as we don't yet have the tools to get rid of more. We'll come to that soon.
def circle(size: Int, color: Color): Image =
Image.circle(size).strokeWidth(3.0).strokeColor(color)
def fadeCircles(n: Int, size: Int, color: Color): Image =
n match {
case 0 => Image.empty
case n =>
circle(size, color)
.on(fadeCircles(n-1, size+7, color.fadeOutBy(0.05.normalized)))
}
def gradientCircles(n: Int, size: Int, color: Color): Image =
n match {
case 0 => Image.empty
case n =>
circle(size, color)
.on(gradientCircles(n-1, size+7, color.spin(15.degrees)))
}
def image: Image =
fadeCircles(20, 50, Color.red)
.beside(gradientCircles(20, 50, Color.royalBlue))