Chapter 9: Variables
The only variables you’ve used so far were the formal inputs to procedures. You can create your own variables to hold important information and use them with Logo commands and your own procedures. Variables are like containers - they contain some “thing” inside. This chapter discusses how to make your own variables, how to get at the “thing” inside, and other important issues about variables that you need to be aware of to make your programming experience successful.
MAKE and THING
Every variable has a name and a value associated with it. A name is a word, just like the words you’ve used for the names of turtles and bitmaps. When you refer to a turtle by its name, you have to put a double quotation mark in front of it. The same is true for the name of a variable - otherwise, Logo will think it’s a command and try to run it. The quotation mark is somewhat like a command - it outputs the word that follows it. Type:
"TIMES
Result: TIMES
To create a variable of your own, use the MAKE command. It needs two inputs: the name of the variable and the value to assign to it. The value of a variable can be any valid Logo data. Type:
MAKE "TIMES 4
To see the variables in your workspace, use the PONS command (short for PRINTOUT NAMES). Type:
PONS
TIMES is 4
Your variable named :TIMES
has the value of 4 associated with it. Or,
4 is the “thing” inside the container named :TIMES
. To use the value
of a variable, give the name of the variable to the
THING command.
THING looks for the variable in the
workspace and outputs the value associated with the name. Type:
THING "TIMES
Result: 4
THING can be used wherever you need a value. For example, type:
REPEAT THING "TIMES [FORWARD 100 RIGHT 360 / THING "TIMES]
At the moment, the value 4 is associated with :TIMES
, so the turtle
draws a square (assuming there is a turtle and the pen is down). The
value of a variable can be changed with the
MAKE command. It works somewhat like the
PPROP command for property lists - if
the variable does not exist, it is created; if the variable already
exists, then a new value is assigned to take the place of the old value.
Type:
MAKE "TIMES 5
Use the uparrow key to run the REPEAT command
again. This time, the turtle draws a pentagon because :TIMES
has the
value 5 now. You probably think that
THING is a rather awkward way of getting
the value of a variable. It seems like an awful lot typing to do,
especially if you use lots of variables in the same command line. You’re
right.
Dots and Relaxed Syntax
Logo has a special shortcut called “dots.” It’s the colon character (:) and it takes the place of both the THING command and the double quotation mark in front of the variable name. The REPEAT command looks much clearer using dots. Type:
REPEAT :TIMES [FORWARD 100 RIGHT 360 / :TIMES]
The use of dots and the double quotation mark is often confusing to beginning Logo programmers. For that reason, Terrapin Logo has an option called Relaxed syntax in the Preferences dialog of the Edit menu. It is checked by default and allows you to “cheat” a little - with Relaxed syntax checked, you don’t need the dots at all! As long as a variable name is not the same as a procedure name, Logo does not have a problem knowing which is a command and which is a variable. The REPEAT command works just fine without dots. Type:
REPEAT TIMES [FORWARD 100 RIGHT 360 / TIMES]
Of course, using dots is like cheating, too, since it’s a shortcut for
THING and the double quotation mark. So
what’s the big deal? Why use dots at all? Well, it’s much easier than
using THING all the time and it allows
you to have a variable with the same name as a procedure. If you tried
to define a procedure to draw a rectangle, and you wanted the inputs to
be called :WIDTH
and :HEIGHT
, you’d have a problem because
WIDTH is the name of the Logo command that
outputs the current width of the turtle’s pen. Define the RECTANGLE
procedure - don’t use dots in front of the variable names.
Now, type the following commands to try to make a rectangle than is 100 steps wide by 40 steps tall:
RECTANGLE 100 40
It looks 40 steps tall, but it certainly is not 100 steps wide. The
default for the turtle’s pen with is 1, so the drawing is the same as if
you had used 1 instead of 100. Set the pen width to 5 and then do the
RECTANGLE
command again. Type:
SETW 5
RECTANGLE 100 40
It doesn’t matter what you use as the first input to RECTANGLE
,
because it is really using the output of the
WIDTH command - not the formal input value
of 100 that you intended to assign to the variable :WIDTH
. If you’re
careful about the variable names you select, you won’t run into this
problem. But, if something unusual is happening, check your variable
names.
Another reason for dots is that it makes the syntax of Logo cleanly consistent, which makes it easier to use. With nothing in front of a word, Logo looks for a command with that name and tries to run it. The dots tell Logo that the value is needed and the quotation mark tells Logo to use the actual word that follows it.
Throughout the rest of this manual, the dots will be used. That does not mean that you have to change the Relaxed syntax option - Logo doesn’t require the dots with Relaxed syntax, but it won’t get upset if you use them. However, if you turn off Relaxed syntax and try to run a procedure that was created with Relaxed syntax turned on, you may have to edit the procedure to put the dots where they belong.
Turn off the Relaxed syntax option and then do the RECTANGLE
command
again. You will see the following message in the Listener window. With
Relaxed syntax turned off, Logo looks for a procedure named HEIGHT
rather than using it as the variable name.
When you read instruction lines or say them out loud, it’s a good idea to say the word “dots” for the colon and “quote” for the double quotation mark. It helps to distinguish where a value is being used and where a name is being used.
Formal Inputs
The formal input to a procedure is called a local variable. It is
created in the procedure’s local environment - it’s like a private
workspace. When the procedure stops, the local variable no longer
exists. Each time a procedure with a formal input is run, the local
variable is created again. Define the FORMAL.SAMPE
procedure:
A formal input is not a value - it’s a character pattern that Logo
will look for when you actually run the procedure in order to substitute
the actual input value in place of the formal input. The dots in front
of the formal input :TIMES
are required to exactly match the character
pattern ::TIMES
in the procedure’s instructions. The formal input to
FORMAL.SAMPLE
has the same name as the variable you created in
your workspace. However, it is not the same variable as yours -
it is a local variable in the private workspace of the FORMAL.SAMPLE
procedure. Type:
FORMAL.SAMPLE 10
20
SHOW :TIMES
5
When FORMAL.SAMPE
is run, it creates a new variable with the name
:TIMES
and assigns the actual input value to it, in this case, the
value 10. Then, the local variable :TIMES
is changed to its current
value multiplied by 2 and that new value is printed - the value 20.
Your variable called :TIMES
was not used or changed by
FORMAL.SAMPE
.
You can use your :TIMES
variable as an actual input to
FORMAL.SAMPLE. Type:
FORMAL.SAMPLE :TIMES
10
SHOW :TIMES
5
Your variable :TIMES
was used only to get the value assigned to
it. The FORMAL.SAMPE
procedure created its own :TIMES
variable
again. Local variables are, in a sense, temporary - they exist only for
as long as they are needed. It helps to keep your workspace free of
clutter.
Global Variables
Your workspace is called the global environment and the variables
created there are called global variables. Any procedure you run can
make use of your global variables. Define the GLOBAL.SAMPLE
procedure:
The GLOBAL.SAMPLE
procedure does not have an input, but it uses a
variable named :TIMES
. In fact, it’s going to use your variable.
Type:
GLOBAL.SAMPLE
10
SHOW :TIMES
10
Your variable :TIMES
has been changed by the GLOBAL.SAMPLE
procedure. This can be both good and bad. Using a global variable is
often convenient - just create a global variable in your workspace and
your procedures can use it without your having to type its name as an
actual input. However, a global variable can be a subtle cause of bugs
in your procedures, too. Since any procedure can use the global
variable, its value can be changed by any procedure as well.
Additional LOCAL Variables
When you use the MAKE command in the Listener window, you always create a global variable in your workspace. When a procedure uses the MAKE command, it creates or modifies a global variable unless the variable is a formal input - in that case, the procedure modifies the local variable that was automatically created when the procedure was started.
A procedure can create additional local variables in its local
environment. It’s a two-step process: (1) the
LOCAL command must be given the name of the
variable to prevent its creation in the global environment, and (2) the
MAKE command is used to create the
variable and assign a value to it. Define the LOCAL.SAMPLE
procedure:
The variable :TIMES
is created in the procedure’s local environment.
It does not affect your global variable :TIMES
because the
LOCAL command forces the procedure to use its
own workspace when it refers to :TIMES
. Type:
LOCAL.SAMPLE 40
80
SHOW :TIMES
10
Because additional LOCAL variables are created in the procedure’s local environment, they no longer exist when the procedure stops. Without LOCAL, a procedure could create or modify variables in the global environment which could get quite cluttered with variables. You might have a difficult time trying to figure out where they came from and which procedure is using which variables.
Dynamic Scope of Variables
The “scope” of a variable is essentially its “life cycle.” When is it
created? Where is it created? How long does it exist? Who has access to
it? These are all questions that you need to be aware of when using
variables. Most of the questions have already been answered in the
earlier sections of this chapter. However, the situation gets a bit more
complicated when one procedure calls another. Define the procedures
DYNAMIC.SUPER
and DYNAMIC.SUB
:
When you run DYNAMIC.SUPER
, the formal input will create a local
variable :TIMES
in the local environment of DYNAMIC.SUPER
. The value
of the formal input is printed before and after calling DYNAMIC.SUB
.
DYNAMIC.SUB
does not have a formal input. It looks very much like the
GLOBAL.SAMPLE
procedure used earlier. However, DYNAMIC.SUB
is a
subprocedure that is called by DYNAMIC.SUPER
. The rules of dynamic
scope control where a procedure looks for the variables that it needs.
In this case, DYNAMIC.SUB
will look first in its own local environment
for a variable named :TIMES
. Since there is not one there, it then
looks in the local environment of the procedure that called it, in this
case, the local environment of DYNAMIC.SUPER
.
Type:
DYNAMIC.SUPER 100
BEFORE 100
SUB 200
AFTER 200
SHOW :TIMES
10
It is the local variable :TIMES
that is modified and printed by
DYNAMIC.SUB
. Your global variable :TIMES
is not affected. However,
what happens if you run DYNAMIC.SUB
yourself, rather than having
DYNAMIC.SUPER
run it? Type:
DYNAMIC.SUB
SUB 20
SHOW :TIMES
20
Oh no! This time DYNAMIC.SUB
used your global variable. Since
DYNAMIC.SUB
has no formal input or local variable with the name
:TIMES
, it looks in the environment of the procedure that called it -
in this case, it’s the global environment of Logo.
Does this mean you shouldn’t use global variables at all? No, but you need to be aware of where variables are created and who has access to them. Global variables can be convenient, but they can also be the cause of subtle, hard-to-find bugs.
Formal inputs can also cause subtle problems. If you expect a subprocedure to modify a variable, but you define the subprocedure with a formal input with the same name as the variable you want modified, it won’t be modified at all because the subprocedure will create its own local variable. Does that mean you shouldn’t use formal inputs in a subprocedure? No, but it might be better to have the subprocedure compute a new value and then use OUTPUT to report the new value rather than expect the subprocedure to modify a variable.
Indirect Reference
When you use dots in front of the name of a variable or when you use THING and the quoted name of a variable, you are making a direct reference to the value of the variable. The value of one variable can be the name of another variable. To obtain the value of the variable whose name is the value of the first variable, you need to use what is called indirect reference. It may seem somewhat complicated, but it’s a useful concept to understand.
Suppose you’re making a game that keeps score in variables called :ME
and :YOU
. As the game is played, you could check whose turn it is and
award points to the proper player with something like this:
IF :PLAYER = "ME [MAKE "ME 10 + :ME] [MAKE "YOU 10 + :YOU]
To follow the examples, enter the commands to initialize :ME
and
:YOU
to zero points and set the current player to :ME
. Type:
MAKE "ME 0
MAKE "YOU 0
MAKE "PLAYER "ME
Since the value of :PLAYER
is the word :ME
, and the
THING command needs a word as input, then
you can refer to the variable named :ME
by using an indirect
reference with the THING command.
THING "PLAYER
Result: ME
THING THING "PLAYER
Result: 0
Now, when the current player earns points, you can just add the points without checking whose turn it is by using a command like this:
MAKE THING "PLAYER 10 + THING THING "PLAYER
And, since the dots can take the place of THING and the quotation mark, you could use the following form of the command:
MAKE :PLAYER 10 + THING :PLAYER
And, since the dots are really a shortcut for THING when it’s given a word as input, you could use the following form of the command, too:
MAKE :PLAYER 10 + ::PLAYER
Using more than one set of dots in front of a variable name works just fine, but it may be too subtle, even after you fully understand what is going on. It’s probably best to use THING to more clearly show that you are using an indirect reference.
An Indirect Reference to Recursion
An indirect reference can go deeper than one level. For example, consider the idea of wrapping a gift in a small box wrapped in a big box wrapped in a huge box wrapped in a gigantic box wrapped up and given as a present to someone. How does the person get at the gift? A sequence of MAKE commands can simulate “wrapping” the gift. Type:
MAKE "SMALL.BOX "GIFT
MAKE "BIG.BOX "SMALL.BOX
MAKE "HUGE.BOX "BIG.BOX
MAKE "GIGANTIC.BOX "HUGE.BOX
MAKE "PRESENT "GIGANTIC.BOX
To “unwrap” the present, the person can look inside with THING or use dots. Type:
SHOW THING "PRESENT
GIGANTIC.BOX
SHOW :PRESENT
GIGANTIC.BOX
It’s really another present inside, just little smaller! The person can continue to “unwrap” the present by looking inside the THING that was inside the THING that was in the original present. Type:
SHOW THING THING "PRESENT
HUGE.BOX
SHOW ::PRESENT
HUGE.BOX
The present is getting smaller, but there’s no gift yet. This kind of “gift wrapping” is somewhat like recursion. Maybe what the person needs is a procedure that can do the “unwrapping” by continuing to “unwrap” presents until a gift is found. Here is an example of a procedure to do just that.
The UNWRAP
procedure does what a person would do to unwrap a
recursively-wrapped present. With each unwrapping, the person shows
everyone else what was inside. The person is expecting a gift
(eventually), so after unwrapping a present, the person checks to see if
the gift was inside. If so, the person can stop unwrapping and say how
much the gift is appreciated. If the gift is not found, then the person
has another (slightly smaller) present to unwrap and the whole process
is done again. Finally, a gift will be found and the unwrapping can
stop. Type:
UNWRAP :PRESENT
GIGANTIC.BOX
HUGE.BOX
BIG.BOX
SMALL.BOX
GIFT
JUST WHAT I WANTED
Recursion is a very powerful tool for programming. It’s also something you do all the time without knowing it. For example, when you eat a bowl of ice cream, you take a scoop with a spoon and eat some of it. What you have left is a bowl of ice cream with slightly less ice cream in it. Every time you take a scoop, you are asking yourself “Am I done yet?” If you are done, you stop scooping and put the bowl and spoon into the dishwasher (right?). Of course, the real-life recursion you do is so natural, you probably never thought of it as a recursive process at all. Typically, what you are doing is called a tail-recursive process because you are accomplishing a task all by yourself. In programming, tail recursion is also possible but recursion, in general, can get a bit more complicated.
A procedure often does one part of a task and then calls on another procedure to help with the next step - it just happens that the extra help is performed by a procedure with the same name as the first procedure. If you could make temporary clones of yourself to help you eat your ice cream, you and your clones would be like the procedure that needs extra help. After your first spoonful, a clone would take the next spoonful, then another clone would take the next, and so on, until the ice cream was gone. The last (disappointed) clone would turn to the previous clone and say “It’s all gone!” Each clone would pass this message along to the previous clone and then disappear. Finally, the message would get back to you and you’d put the bowl and spoon into the dishwasher (right?). Chances are, you’d want more ice cream. You may want to know more about recursion, too. It can be a satisfying experience.