Class summary:   Introduction to Functions
1 Repeated Computations:   Flags Example
2 Defining Your Own Functions
3 Exercises
3.1 Pen-cost
3.2 Repeated ranges
4 The Design Recipe

Class summary: Introduction to Functions

Copyright (c) 2017 Kathi Fisler

This material goes with From Repeated Expressions to Functions from the textbook

1 Repeated Computations: Flags Example

Consider programs to draw the flags of Madagascar and Benin. These two countries have the same flag, just with different colors:

  madagascar =

    frame(

      beside(rectangle(40, 80, "solid", "white"),

        above(rectangle(70, 40, "solid", "red"),

          rectangle(70, 40, "solid", "green"))))

  

  benin =

    frame(

      beside(rectangle(40, 80, "solid", "green"),

        above(rectangle(70, 40, "solid", "yellow"),

          rectangle(70, 40, "solid", "red"))))

Rather than write this code twice, it would be nice to write the common code only once, then just change the colors to generate each flag. With what we’ve learned so far, we might do this as follows:

  side-color = "white"

  top-color = "red"

  bot-color = "green"

  

  flag=

    frame(

      beside(rectangle(40, 80, "solid", side-color),

        above(rectangle(70, 40, "solid", top-color),

          rectangle(70, 40, "solid", bot-color))))

This is unsatisfying, however – what if we were trying to create an image that included both flags at the same time? We can only put one defintion of each color in the Pyret dictionary, so there is no way to change the colors to generate another flag within the same run of the program.

Step back for a moment and think about num-min. If Pyret didn’t provide num-min, could you write that computation by hand?

  num1 = 4

  num2 = 5

  

  if (num1 < num2):

    num1

  else:

    num2

  end

This has the same problem as our flags example: we’d have to edit the num1 and num2 definitions every time we wanted to compute the min. Fortunately, Pyret gives us this more convenient notation in which we can provide the two numbers as arguments to num-min. Instead, we can write:

  num-min(4, 5)

Wouldn’t it be nice to be able to do something similar for the flags? It would be nice to just write the following:

  madagascar = sidebar-flag("white", "red", "green")

  benin= sidebar-flag("green", "yellow", "red")

This is precisely what you are about to learn how to do. You are going to learn how to add your own operators to Pyret.

2 Defining Your Own Functions

(See the textbook here for details on what a function is – what follows is just summarizing the code from class)

Here is

  fun sidebar-flag(side-color, top-color, bot-color):

    doc: "produce image of flag with sidebar and two equal-sized horizontal regions"

    frame(

      beside(rectangle(40, 80, "solid", side-color),

        above(rectangle(70, 40, "solid", top-color),

          rectangle(70, 40, "solid", bot-color))))

  end

With this, the two desired flag expressions work fine:

  madagascar = sidebar-flag("white", "red", "green")

  benin= sidebar-flag("green", "yellow", "red")

What if we make a mistake, and try to use the function as follows:

  sidebar-flag(50, "solid", "red")

What do you think should happen? There are two errors here: "solid" is not a color and 50 is not a string (much less a string naming a color). Pyret will give an error on one of the rectangle commands (we’ll see which one in particular in a little while). The point is that the error comes up inside the computation that creates the flag.

Remember what we did with types on table columns though? Once we put types on tables, Pyret gave an error as soon as we tried to create a row with the wrong type of data. Similarly, we can put types on the parameters of a function:

  fun sidebar-flag(side-color :: String, top-color :: String, bot-color :: String):

    doc: "produce image of flag with sidebar and two equal-sized horizontal regions"

    frame(

      beside(rectangle(40, 80, "solid", side-color),

        above(rectangle(70, 40, "solid", top-color),

          rectangle(70, 40, "solid", bot-color))))

  end

Which of the two errors from our mistaken call does this catch? Just the use of 50. What can you do about the use of "solid"? Nothing just yet, but we’ll get there.

3 Exercises

3.1 Pen-cost

Imagine that you are trying to compute the total cost of an order of pens with slogans (or messages) printed on them. Each pen costs 25 cents, plus an additional 2 cents per character in the message (count spaces as characters for now).

First, let’s write two expressions that do this computation, just to make sure we understand how it should work:

  # if ordering 3 pens that say "wow"

  3 * (0.25 + (string-length("wow") * 0.02))

  

  # if ordering 10 pens that say "smile"

  10 * (0.25 + (string-length("smile") * 0.02))

Having a clear idea of the computation you want to do makes it much easier to write a function to do it. How do we figure out a function that would correspond to these two expressions? We find the places where the two functions are different and replace each spot of difference with a unique descriptive name. These names become the parameters of the function.

  fun pen-cost(num-pens :: Number, message :: String):

    num-pens * (0.25 + (string-length(message) * 0.02))

The initial expressions we developed give rise to our examples. When you write the expected results of the examples, you can either write the final answer or the expression that computed it (each can be useful in certain cases). Here’s the final function:

  fun pen-cost(num-pens :: Number, message :: String):

    doc: "total cost for pens, each 25 cents plus 2 cents per message character"

    num-pens * (0.25 + (string-length(message) * 0.02))

    where:

      pen-cost(3, "wow") is 0.87

      pen-cost(10, "smile") is 10 * (0.25 + (string-length("smile") * 0.02))

What are other good cases to check here? Ordering 0 pens and pens with empty messages are both good cases to check. You don’t have to check negative numbers of pens or messages that aren’t strings though – those fall outside the expected "contract" of the function (we’ll discuss how to properly handle the negatives later).

Once you are comfortable with the idea of writing functions, you would skip this circle-and-label step, and just jump directly to writing the expression with the parameters in place. Use the circle-and-label technique as needed to get yourself to that point.

3.2 Repeated ranges

Revisit our scoville/pepper program. There we had several if/else-if checks with a very similar structure. Introduce a function named in-range that checks whether a given number is within the range (inclusive) bounded by two given numbers. Remember to write examples.

  fun in-range(num :: Number, low :: Number, high :: Number) -> Boolean:

    doc: determines whether first num between second and third (inclusive)

    (low <= num) and (num <= high)

  where:

    in-range(0, 0, 5) is true

    in-range(5, 0, 5) is true

    in-range(10, 0, 5) is false

    in-range(-4, 0, 5) is false

    in-range(3, 2, 5) is true

  end

Questions:

4 The Design Recipe

As we wrked the following exercises in class, we relied on the the Design Recipe, a sequence of steps for developing functions. We are going to rely heavily on these steps as the course goes on. Even if the steps seem unnecessary now while we are writing simpler functions, it is important for you to develop the habits of the steps now, before we get into harder problems. Homeworks and quizzes will expect that you know and can apply the steps of the design recipe.