Lecture notes: How functions evaluate, introduction to tables
How functions evaluate
How does Pyret evaluate the following program?
fun f(x :: Number): x + 3 end fun g(y :: Number, z :: Number): y - z end f(f(g(32, 4)))
Pyret evaluates function calls by:
- Matching the inputs in the call to the function’s parameters
- Substituting these inputs for the matching parameters in the function’s body
- Evaluating the resulting expression.
We can combine this with what we learned earlier about the order in which Pyret evaluates expressions (top-down, innermost-outermost, left-right) to see how Pyret evaluates a program. See the lecture capture for an example of doing this for the program above.
Note that because the first thing Pyret does when evaluating a function call is to substitute the inputs for the function’s parameters, the names we choose for parameters don’t matter–at least to Pyret! You should still provide descriptive names to function parameters for readability.
Introduction to tables
So far we have seen programs that manipulate numbers, strings, and images. We’ve also seen Booleans, which are used to represent the answers to yes/no questions. Here are some data that can be represented with what we’ve seen so far:
- A picture of a dog (Image)
- The population of Azerbaijan (Number)
- The mass of Saturn (Number)
- The complete text of the Baghavad Gita (String)
- Whether or not I ate breakfast this morning (Boolean)
There’s a lot we can do with what we’ve seen so far! We can make images; we can compute costs; in lab you’ll even see how to make animations. There are still data, however, that we’d have trouble representing. What if we wanted to write a program to look up the population of any town in Rhode Island (as measured in either 2000 or 2010, the last two census years)? Here’s a first attempt at such a program:
fun population(municipality :: String, year :: Number): if municipality == "Providence": if year == 2000: 173618 else if year == 2010: 178042 else: raise("bad year") end else if municipality == "Cranston": if year == 2000: 79269 else if year == 2010: 80387 else: raise("bad year") end else: raise("bad municipality") end end
raise("message")
stops Pyret from evaluating from the program, and displays an
error message to the user. It’s useful when dealing with unexpected function
inputs.
This program does some of what we want it to–we can use it to get population data. However, it has some problems:
- Rhode Island has 39 municipalities. This function handles 2. How would we scale it up?
- What if we want to do something else, like tracking population growth or density? Would we repeat these data in another function?
We can instead represent the same information (as well as some additional data) as a table. Tables are used for tabular data, like you might find in a spreadsheet:
include tables include shared-gdrive("cs111-2018.arr", "1XxbD-eg5BAYuufv6mLmEllyg28IR7HeX") municipalities = table: name, city, population-2000, population-2010 row: "Providence", true, 173618, 178042 row: "Cranston", true, 79269, 80387 row: "Coventry", false, 33668, 35014 row: "Warwick", true, 85808, 82672, row: "North Providence", false, 32411, 32078 end
Some municipalities are technically “cities,” while others are “towns.” In New England, as opposed to other places in the country, this is mostly just a matter of what a municipality chooses to call itself: see this informative Wikipedia article. This table has 5 of Rhode Island’s municipalities, but it should be clear how to extend it to include the rest (and soon we’ll see how to instead load the information from an external source).
Now that we have the data in Pyret, we can write programs to answer
questions. For these table operations, we’re using our own library instead of
Pyret’s built-in table operations (that’s what that include shared-gdrive(...)
line is doing above). This library is documented here; it’s linked from the
“Software” page on the course website, and we’ll make sure to link to it from
labs, homeworks, and projects where you’re working with tables.
We can get the nth row of the data:
> municipalities.row-n(0) > municipalities.row-n(1) > municipalities.row-n(0)["name"]
We can sort the data by 2010 population:
> sort-by(municipalities, "population-2010", false)
To find the least populous municipalities, we can change the last argument:
> sort-by(municipalities, "population-2010", true)
We can also find all of the cities:
fun is-city(r :: Row) -> Boolean: doc: "get the value in the row's city column" r["city"] end > filter-by(municipalities, is-city)
Note what we’re doing here: we’re passing a function to another function. How is
is-city
used?
We can find municipalities whose population went down:
fun population-decreased(r :: Row) -> Boolean: doc: "returns true if the municipality's population went down between '00 and '10" r["population-2010"] < r["population-2000"] end > filter-by(municipalities, population-decreased)
And, we can combine all of these operations. How would we get the city with the smallest population?
> sort-by(filter-by(municipalities, is-city), "population-2010", true).row-n(0)