Day 4 - Functions
Skills: 1
Pre-reading: 3.3
Intro (10 mins)
Goal Understand how to capture repeated expressions as functions, & the first two parts of function design recipe: type annotations & doc string.
-
Functions allow us to express repeated expressions that differ in some way.
-
e.g., I can write a function that says "Welcome to class, " and then someones name, and then use it for many different people.
fun welcome(name):
"Welcome to class, " + name
end -
Just like definitions, functions are added to the program directory. When you use a function:
welcome("some-name")
(ask for a name from the room) the expression evaluates to the body of the function, with the arguments added to a local version of the program directory for just the function body. -
i.e., in the above example, at the start of the program the program directory is empty. (DRAW A DIAGRAM) After evaluating the
fun...end
, the program directory now has a single entry, which associateswelcome
to a function value. Evaluatingwelcome("some-name")
next turns into"Welcome to class, " + name
, but withname -> "some name"
added to the program directory temporarily. Now when we evaluate the expression,name
will be replaced with "some-name" and then the two strings will be put together. After that string is produced, as a value, the body of function has ended, and thusname
is removed from the program directory. This means that if you try to usename
after, it won't work. -
Observe what happens if you call
welcome
with a number. You get an error, but where is the error pointing to? -
The issue is that since
welcome
doesn't indicate anything about the type of argument it expects, the error that Pyret reports is from inside -- in a call to+
-- when really, the problem is the call towelcome
. -
To fix this, we should put type annotations on all functions we write -- both to communicate to people reading the code what type of values we expect, but also to allow Pyret to report better errors in case of mistakes. We should also indicate the type of value we are going to return!
fun welcome(name :: String) -> String:
"Welcome to class, " + name
end -
This is another example of Design -- and is the first step of our actual design recipe for functions. All functions should have type annotations, and they should actually be the first thing that we figure out, as until we understand the type of data that we are working with, trying to do anything else is likely not going to go well.
-
Another thing that all functions should have, and the second step in our design recipe, is a "documentation string" (or docstring) -- this is a concise, English explanation of what the purpose of the function is. Not necessarily how it works (though the distinction is subtle), but what someone who wants to use it needs to know.
-
Further refining our example:
fun welcome(name :: String) -> String:
doc: "Returns a greeting addressed to the given person"
"Welcome to class, " + name
end
Class Exercise (40 mins)
-
Your goal is to draw an image of a three layer cake. Each cake consists of three equally sized rectangular layers, and the only difference is the color (or "flavor") of each layer. You can choose the colors!
-
Now, draw another cake with differetn flavors (i.e., colors). Notice what is different, and what is the same.
-
Define a new function,
three-layer-cake
that takes as arguments the three parts that change: the color of the top, middle, and bottom colors. -
Be sure to give it a type annotation and docstring.
-
Now use this new function to rewrite both of your past examples, and notice how much easier it is to read even though the behavior is the same: this is your second example of Design -- just like constant definitions can make expressions easier to read, by splitting large expressions into smaller parts or giving meaning to numbers, functions give names to repeated computations.
-
Your next task is to write a function to calculate the cost of printing a t-shirt, which can
have a message written on it. Each shirt costs $5.00 plus an extra $0.10 per character printed on it. -
First, write an expression for 4 t-shirts with the message "Go Team!".
-
Now make another expression, this time for 7 t-shirts with the message "Hello World"
-
Are there are numbers that should be definitions? So-called "magic numbers" that aren't obvious what they mean are always good candidates for creating definitions -- do that now (there should be two!).
-
Now, look at the two expressions, and figure out what changes between the two, and write a function,
tshirt-cost
, that takes as arguments the parts that change. -
Start with the typo annotation -- think about what type the arguments should be. Next write down a docstring. Finally, write the expression for the body! Be sure to only include the part that changes, so that once you rewrite your original expressions to use your new function, the only repeated part is the name of the function!
Wrap-up (10 mins)
- Functions allow us to not repeat ourselves; more importantly, they are our first, and in this class, primary, example of a mechanism for abstraction -- i.e., building a re-usable piece of a program, that has internals that are not visible to other parts of the program. Abstraction is one of, if not the most, important ideas in computer science.
- Type annotations serve as a form of documentation to explain what kind of input each parameter expects.
- Docstrings communicate what the function does, in a clear, simple way.
- These serve both as a guide to others, but more importantly, as a guide to YOU! As thinking through the inputs / outputs before writing the function, and describing what the function should do, helps you figure out how to implement it.