3 Functions
3.1 Abstracting Expressions
Suppose we want to know how much we would weigh when we get to the
moon. As you might know, our moon weight is one-sixth that on earth
(suggesting an especially expensive but effective dieting
strategy). Thus, the moon weight of a person who weighs 100 pounds or
kilograms would be 100 * (1 / 6). If instead a person weighed
150 (the units don’t really matter), their moon weight would be
150 * (1 / 6). Suppose they weighed 200 on earth? Then on the
moon, it would be 200 * (1 / 6). And so on.
Already, we see that it is annoying to have to keep writing 1 /
6. It would be much better to give that expression a meaningful name
and use its name everywhere. This would:
improve the program’s readability, by giving this mysterious
number a name that explains it;
reduce error, because we if we type it each time, we might
accidentally mis-type it one time (and rely on this incorrect
answer); and,
easily update our knowledge: if we find the answer needs to
change (for instance, we might improve its precision), we would need
to make that change in only one place.
This is such a common and important concept in writing software that
it has a name: the DRY Principle, where DRY stands for
“Don’t Repeat Yourself”.
Therefore, we can rewrite the preceding sequence of expressions as:
earth-to-moon = 1/6 |
100 * earth-to-moon |
150 * earth-to-moon |
200 * earth-to-moon |
This is much better, but notice we’ve again violated the DRY
principle: the entire expression something * earth-to-moon, for
different values of something, has been repeated.
3.2 Functions as Expression Abstractions
To avoid this kind of repetition, we apply a similar idea: give a name
to the repeated expression. However, what is in common needs to have a
“hole” to fill in the part that differs. This hole is called a
argument or parameter, and the resulting named
expression—with one or more holes in it—is a
function. Here’s the function that captures the above pattern:
fun moon-weight(earth-weight): |
earth-weight * earth-to-moon |
end |
We can use it to compute the preceding three values as follows:
moon-weight(100) |
moon-weight(150) |
moon-weight(200) |
Notice that the arguments for the DRY principle apply again: we’ve
improved readability, reduced error, and given ourselves a single
place at which to improve the program.
3.3 Testing
Now that you’ve found this better way to write the computation, is
there any use for the old expressions? There certainly is! We now have
two different ways of computing what is supposedly the same answer. We
can thus ask Pyret to check that the answers are in fact the
same. This process is called testing.
Specifically, in Pyret we can write check blocks that contain
tests. Pyret will run all the tests and report on whether or not they
succeeded:
check: |
moon-weight(100) is 100 * earth-to-moon |
moon-weight(150) is 150 * earth-to-moon |
moon-weight(200) is 200 * earth-to-moon |
end |
Of course, we could have written these tests differently. For
instance, the middle test could have been written as
moon-weight(150) is (150 * (1 / 6)) |
or even
Which form we choose is a function of what our goal is in testing:
what kind of changes we expect the program to undergo, which data we
can establish some other way, and so on. Over the course of our study
we will learn a great deal more about testing. For now, the key
message to remember is that the heart of testing is redundancy:
saying something two different ways with the expectation that the two
ways will produce the same outcome.
3.4 Annotations
When we define a function, we have in mind some kind of data the
function is supposed to be supplied, and some kind of datum it will
produce. Though Pyret does not require it, it would be nice to write
down these expectations in the program itself; if we do, as a bonus,
Pyret will check them for us. There are many places in a Pyret program
where we can place annotations, but the most common are on the
parameters of a function, and on the kind of value it will
return. Here are annotations on the preceding function:
fun moon-weight(earth-weight :: Number) -> Number: |
earth-weight * earth-to-moon |
end |
The annotation earth-weight :: Number says that the parameter
must be a number, while -> Number after the parameters and
before the body says that the function computes a number, too.
Do Now!
Change these annotations to something else and see what happens. For
instance, try using String instead of Number in each
of the annotation positions.