Class summary:   Function Evaluation and Practice
1 Warm-Up:   Ticket Prices
2 How Functions Evaluate
2.1 Information Tracked During Evaluation
2.2 Substitute Arguments into Function Bodies
2.3 Handling Expressions as Arguments
3 Evaluating Functions and If-Expressions
4 How Functions Affect the Dictionary
5 Defining Versus Calling Functions
6 More Functions Practice

Class summary: Function Evaluation and Practice

Copyright (c) 2017 Kathi Fisler

This builds on the material in
This material goes with From Repeated Expressions to Functions from the textbook
but adds detail to what’s in the textbook.

1 Warm-Up: Ticket Prices

A sports stadium offers special ticket pricing for large groups. The first 10 tickets cost $15 each, and additional tickets cost $10 each. Develop a function ticket-price that takes a number of tickets and produces the total cost of that many tickets.

Identify the name, parameters, body, and header of your definition.

2 How Functions Evaluate

We have talked about how expressions evaluate: from the innermost-expression out, with definitions extending the dictionary. But what happens when we add our own functions? How do programs with functions evaluate? We’ll work this first with a small function, then revisit on the ticket-price function.

2.1 Information Tracked During Evaluation

As a reminder, Pyret tracks two pieces of information at all times:

As we look at how functions affect evaluation, our goal is to be clear on how each of defining and using functions affects these two pieces of information.

2.2 Substitute Arguments into Function Bodies

Assume that we have the following definition, and want to evaluate the expression 5 + cube(2).

  # contents of definitions window

  fun cube(n :: Number):

    n * n * n

  where:

      cube(1) is 1

      cube(4) is 64

  end

Evaluation still goes inside out (and left-to-right), but when we get to call to a user-defined function, we substitute the argument into the body of the function, then continue evaluating. Here’s what the evaluation looks like step-by-step:

  5 + cube(2)

  

  5 + (2 * 2 * 2)

  

  5 + 8

  

  13

In the 2nd line, we replaced the call to cube{2} with the body of cube, but with each use of n replaced with the argument 2. Once the substitution occurs, evaluation continues using the same "inside-out" rule that we had before.

2.3 Handling Expressions as Arguments

What if we had the program cube(3 + 1). Would the first three steps look like:

  cube(3 + 1)

  

  cube(4)

  

  4 * 4 * 4

or

  cube(3 + 1)

  

  (3 + 1) * (3 + 1) * (3 + 1)

  

  4 * 4 * 4

or

  cube(3 + 1)

  

  (3 + 1) * (3 + 1) * (3 + 1)

  

  4 * (3 + 1) * (3 + 1)

The first sequence is correct. By the inside-out rule, Pyret will evaluate the argument (3+1) before it substitutes in the body of the function.

3 Evaluating Functions and If-Expressions

Combining functions and if-expressions doesn’t introduce anything new, but it does reinforce the rules we’ve already learned.

Go back to the ticket-price function and show the evaluation steps for the expression that follows the function definition.

  fun ticket-price(num-tix):

    if (num-tix > 10):

      (15 * 10) + ((num-tix - 10) * 10)

    else:

      num-tix * 15

    end

  where:

    ticket-price(10) is 150

    ticket-price(2) is 30

    ticket-price(12) is 170

  end

  

  ticket-price(12)

ticket-price(12)

if (12 > 10): (15 * 10) + ((12 - 10) * 10) else: 12 * 15 end

if true: (15 * 10) + ((12 - 10) * 10) else: 12 * 15 end

(15 * 10) + ((12 - 10) * 10)

150 + ((12 - 10) * 10)

150 + (2 * 10)

150 + 20

170

4 How Functions Affect the Dictionary

We have talked about how definitions impact the dictionary of known names within Pyret. How do functions affect the dictionary? Consider the following definitions window contents:

  outside = 100

  

  fun colored-square(color):

    square(outside, "solid", color)

  end

Running this program adds two names to the dictionary: outside and colored-square. The fun construct creates dictionary entries, just as = does.

5 Defining Versus Calling Functions

Does a red square get created when we run the definitions window from the previous section? No. All the fun construct does is augment the dictionary with a new operator.

We only get a red square when we use or call the function. For example, if we type

  colored-square("red")

in the Interactions window, Pyret evaluates the expression with the following sequence:

  colored-square("red")

  

  square(outside, "solid", "red")

  

  # which produces the image

It is essential to understand that defining and calling functions are two separate actions. Defining a function augments the dictionary, but the function body does not get evaluated when a function is defined. Calling a function evaluates its body.

A function gets defined only once, but it may be called many times, which means its body may be evaluated many times. To see this, consider the following sequence:

  cube(2) + cube(3)

  

  (2 * 2 * 2) + cube(3)

  

  8 + cube(3)

  

  8 + (3 * 3 * 3)

  

  8 + 27

  

  35

The distinction between defining and calling functions is a common stumbling block when people learn programming. Keep the dictionary in mind: we define functions in order to name computations that we’ll use multiple times. Naming is separate from using.

6 More Functions Practice

Here are several problems with which to practice both defining and evaluating functions. Make sure to use the Design Recipe while designing each one, thinking about a good set of examples.

Develop the function image-classify, which consumes an image and conditionally produces "tall" if the image is taller than wide, "wide" if it is wider than tall, or "square" if its width and height are the same.

[Problem credit: How to Design Programs, 2nd edition]

Develop the function custom-message, which takes the name of a person and a discount to offer to that person. The function produces a string consisting of a message telling the person what discount they can get by shopping today. Here’s an example:

  custom-message("Kathi", 10) is

  "Hi Kathi! Shop today and get a 10% discount"

Pyret has a built-in function called to-string that converts any value into a string. You may find that helpful here.

You want to split the cost of a pizza with friends. Each of you will pay for the number of slices you ate, as well as your share of the tip on those slices. Assume a pizza costs $12 and has 8 slices. Write a function pizza-share that consumes the number of slices that you ate and returns your share of the cost of the pizza including a 10% tip.

  fun pizza-cost(slices):

    doc: "compute cost of given number of pizza slices, plus 10% tip"

    (12 * (slices / 8)) * 1.10

  where:

    pizza-cost(8) is 12 * 1.10

    pizza-cost(4) is 6 * 1.10

  end

This function actually has two tasks in it — computing your share of the the cost of the slices, and computing your tip on that share. When you have multiple tasks in the same problem, it often makes sense to have each task be its own function. That can make the code easier to read, and tasks are sometimes reusable (you might add a tip onto many different computations, for example). So go one step further and make a separate function for the tip task.

  fun tip(amt):

    amt * 1.10

  where:

    tip(1) is 1.10

    tip(2) is 2.20

    tip(5) is 5.50

  end

  

  fun pizza-cost(slices):

    doc: "compute cost of given number of pizza slices, plus tip"

    tip(16 * (slices / 8))

  where:

    pizza-cost(8) is 13.20

    pizza-cost(4) is 6.60

  end

Note that we also write examples for helper functions such as tip. Why? Two reasons: (1) we still want examples to confirm that the function is working properly, and (2) if we get the wrong answer from pizza-cost and have not checked tip, we won’t know which function contains the error.

Recall the gradebook programs we wrote on tables last week. Develop a function called trajectory that takes in a student’s grades on the first two exams and produces a string that summarizes the direction of the student’s exam grades. If the two grades are within 5 points of each other, the direction is "steady". Otherwise, if the second exam is higher, the direction is "improving"; if the first exam is higher, the direction is "declining".

Load up the gradebook table from last week, and use the trajectory function to extend the gradebook with a new column that reports the trajectory.

How good were your test cases for trajectory? Open this Pyret file, which contains three solutions to this problem (submitted by students in a previous course). Did your examples pick up that there were errors in these solutions? To find out, paste your where clause into each of these definitions (changing the name of the trajectory function to match which function you are testing). Do any of your examples fail? If not, then your examples didn’t cover the situation in which the solution was wrong. Add examples until you find one that shows the error in the solution, then try to describe more generally what situation had the error.

The dates of the astronomical seasons vary from year to year and from location to location. For Providence this year, the seasons are as follows:
  • spring: Mar 20 - Jun 19

  • summer: Jun 20 - Sept 21

  • fall: Sept 22 - Dec 20

  • winter: Dec 21 - Mar 19

Develop a function called season that consumes a month (as a String) and a day (a Natural). The function produces the season (a String) for that date.

Note: there are different ways to break this problem down into functions and helper functions. Spend some time thinking about how you might do this, rather than diving in to start coding.

[Problem credit: Glynis Hamel, WPI CS]