4 Naming Values
4.1 The Definitions Window
So far, we have only used the interactions window on the right half of the CPO screen. As we have seen, this window acts like a calculator: you type an expression at the prompt and CPO produces the result of evaluating that expression.
The left window is called the definitions window. This is where you can put code that you want to save to a file. It has another use, too: it can help you organize your code as your expressions get larger.
4.2 Naming Values
The expressions that create images involve a bit of typing. It would be nice to have shorthands so we can “name” images and refer to them by their names. This is what the definitions window is for: you can put expressions and programs in the definitions window, then use the “Run” button in CPO to make the definitions available in the interactions window.
Do Now!
Put the following in the definitions window:
include image red-circ = circle(30, "solid", "red")
Hit run, then enter red-circ in the interactions window. You should see the red circle.
More generally, if you write code in the form:
NAME = EXPRESSION
Pyret will associate the value of EXPRESSION with NAME. Anytime you write the (shorthand) NAME, Pyret will automatically (behind the scenes) replace it with the value of EXPRESSION. For example, if you write x = 5 + 4 at the prompt, then write x, CPO will give you the value 9 (not the original 5 + 4 expression).
What if you enter a name at the prompt that you haven’t associated with a value?
Do Now!
Try typing puppy at the interactions window prompt (›››). Are there any terms in the error message that are unfamiliar to you?
CPO (and indeed many programming tools) use the phrase “unbound identifier” when an expression contains a name that has not been associated with (or bound to) a value.
4.2.1 Names Versus Strings
At this point, we have seen words being used in two ways in programming: (1) as data within strings and (2) as names for values. These are two very different uses, so it is worth reviewing them.
Syntactically (another way of saying “in terms of how we write it”), we distinguish strings and names by the presence of double quotation marks. Note the difference between puppy and "puppy".
Strings can contain spaces, but names cannot. For example, "hot pink" is a valid piece of data, but hot pink is not a single name. When you want to combine multiple words into a name (like we did above with red-circ), use a hyphen to separate the words while still having a single name (as a sequence of characters). Different programming languages allow different separators; for Pyret, we’ll use hyphens.
Entering a word as a name versus as a string at the interactions prompt changes the computation that you are asking Pyret to perform. If you enter puppy (the name, without double quotes), you are asking Pyret to lookup the value that you previously stored under that name. If you enter "puppy" (the string, with double quotes) you are simply writing down a piece of data (akin to typing a number like 3): Pyret returns the value you entered as the result of the computation.
If you enter a name that you have not previously associated with a value, Pyret will give you an “unbound identifier” error message. In contrast, since strings are just data, you won’t get an error for writing a previously-unused string (there are some special cases of strings, such as when you want to put a quotation mark inside them, but we’ll set that aside for now).
Novice programmers frequently confuse names and strings at first. For now, remember that the names you associate with values using = cannot contain quotation marks, while word- or text-based data must be wrapped in double quotes.
4.2.2 Expressions versus Statements
Definitions and expressions are two useful aspects of programs, each with their own role. Definitions tell Pyret to associate names with values. Expressions tell Pyret to perform a computation and return the result.
Exercise
Enter each of the following at the interactions prompt:
5 + 8
x = 14 + 16
triangle(20, "solid", "purple")
blue-circ = circle(x, "solid", "blue")
The first and third are expressions, while the second and fourth are definitions. What do you observe about the results of entering expressions versus the results of entering definitions?
Hopefully, you notice that Pyret doesn’t seem to return anything from the definitions, but it does display a value from the expressions. When you write a definition, you are telling Pyret to make an entry in its directory that associates names with values. Pyret evaluates the expression on the right side of the =, then stores the resulting value alongside the name. In contrast, if you write just an expression, you are asking Pyret to perform a computation and produce the result.
In programming, we distinguish expressions, which yield values, from statements ,which don’t yield values but instead give some other kind of instruction to the language. So far, definitions are the only kinds of statements we’ve seen.
Exercise
Assuming you still have the blue-circ definition from above in your interactions window, enter blue-circ at the prompt (you can re-enter than definition if it is no longer there).
Based on what Pyret does in response, is blue-circ an expression or a definition?
Since blue-circ yielded a result, we infer that a name by itself is also an expression. When Pyret encounters a name in (or as) an expression, it goes to the directory and looks it up, returning the saved value.
This exercise highlights the difference between making a definition and using a defined name. These two tasks are akin to what happens with a human-language dictionary: at some point, someone made an entry in the dictionary for a word. When you use a dictionary, you are interested in retrieving the meaning or value of that word.
4.3 Using Names to Streamline Building Images
The ability to name values can make it easier to build up complex expressions. Let’s put a rotated purple triangle inside a green square:
overlay(rotate(45, triangle(30, "solid", "purple")), rectangle(60, 60, "solid", "green"))
However, this can get quite difficult to read and understand. Instead, we can name the individual shapes before building the overall image:
purple-tri = triangle(30, "solid", "purple") green-sqr = rectangle(60, 60, "solid", "green") overlay(rotate(45, purple-tri), green-sqr)
In this version, the overlay expression is quicker to read because we gave descriptive names to the initial shapes.
Go one step further: let’s add another purple-triangle on top of the existing image:
purple-tri = triangle(30, "solid", "purple") green-sqr = rectangle(60, 60, "solid", "green") above(purple-tri, overlay(rotate(45, purple-tri), green-sqr))
Here, we see a new benefit to leveraging names: we can use purple-tri twice in the same expression without having to write out the longer triangle expression more than once.
Exercise
Assume that your definitions window contained only this most recent expression (and the include image statement). How many separate images would appear in the interactions window if you pressed Run? Do you see the purple triangle and green square on their own, or only combined? Why or why not?
Exercise
Re-write your expression of the Armenian flag (from Making a Flag), this time giving intermediate names to each of the stripes.
In practice, programmers don’t name every individual image or expression result when creating more complex expressions. They name ones that will get used more than once, or ones that have particular significance for understanding their program. We’ll have more to say about naming as our programs get more complicated.