User Tools

Site Tools


logo:tutorial:chapter9

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 (really) 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.

logo/tutorial/chapter9.txt · Last modified: 2019/01/22 06:09 (external edit)