On this page:
3.1 Abstracting Expressions
3.2 Functions as Expression Abstractions
3.3 Testing
3.4 Annotations

3 Functions

    3.1 Abstracting Expressions

    3.2 Functions as Expression Abstractions

    3.3 Testing

    3.4 Annotations

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

moon-weight(150) is 25

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.