Chapter 11: Playing with Logo
This chapter is about playing with Logo itself - to explore what’s inside and to extend its capabilities in useful ways. Actually, every procedure you write extends Logo in some way that is useful - otherwise, why would you write a procedure at all? Your procedures are really small programs that manipulate data, but you’ll see that your programs can also be the data for another program. The extensions in this chapter are mainly loops for running a list of instructions. Logo has some loop controls already, but there are others that are quite useful, too.
Computers are good at doing the same thing over and over again. That’s what a loop is - the same thing over and over, usually a set of instructions, like what you’ve used in a REPEAT command. There are different kinds of loops in Logo, each with a different way to control them.
The REPEAT command is controlled by a number that tells Logo how many times to repeat a list of instructions, but you don’t have access to the repetition counter within the list of instructions. The FOR command is similar to REPEAT. However, you can control the repetition counter - the initial value, the terminating value, and optionally, the value used to modify the repetition counter each time through the loop. In addition, you can use the repetition counter within the list of instructions. The EACH command is controlled by the WHO list and the instructions are typically for turtles and bitmaps.
The FOREACH command is controlled by a list of words. The instruction list is run for each item in the list of words. Each word is available in the list of instructions through the quoted question mark.
The WHILE loop is controlled by a condition. If the condition is true, the instructions are run and then the condition is tested again. As long as the condition is true, the instructions are repeated.
With all these loops, what else would you need? Here is a series of statements about loops that are not directly programmable in Logo.
- Run some instructions forever.
- Run some instructions when a certain condition becomes true.
- Run some instructions until a certain condition becomes true.
- Run some instructions unless a certain condition becomes true.
Of course, you have run instructions forever a number of times. When you put a procedure’s name as the last line of the procedure definition, you create an infinite loop - the same thing as running the procedure forever.
Sometimes it’s convenient to have instructions run at just the right
time. You used the WAIT command to control the
timing of instructions, but sometimes what you need to wait for is not
the passage of time, but the arrival of a particular event. A
procedure could check for the event and then run the instructions for
you at just the right moment.
The idea of doing some instructions until a condition becomes true seems
like a reasonable thing to do. The problem is, should the instructions
be run at least one time - before checking the condition? Yes, unless
you don’t want them run first. All of these kinds of loops can be
created by using a looping control that has a bad reputation - the
combination of LABEL and
GO. The LABEL command
takes a word as input and creates a marker which is used in a manner
that is similar to the way that
ELSE are used as markers in
an IF command. The GO
command takes a word as input and forces Logo to find the
LABEL command with the matching word and then
continue running instructions from that point instead of running the
next instruction after the GO command.
In some programming languages, the GO command can
force a procedure to continue running instructions anywhere there is a
label to go to. That’s not true for Logo. The
LABEL for a GO must be
within the same procedure as the GO command
and the LABEL must be somewhere before
the GO command. Even with these restrictions,
it’s still possible to get out of control, especially when you use more
than one LABEL or more than one
GO in the same procedure. With a little careful
planning and a lot of self-control, LABEL and
GO can be useful. If they weren’t useful, they
wouldn’t be part of any programming language. The illustration shows
each loop using LABEL and
do not enter them. They are each quite
simple and easy to understand, but there is another way to define them.
You can also define all of these loops with the WHILE command.
WHEN procedure is only going to run its instruction list one time
- not really a loop. However, the looping is done while
WHENis waiting for the condition to become true. The
WHENprocedure is like the opposite of WHILE. For that reason, the
:CONDITIONthat is input to
WHENis reversed with the NOT command.
WHENmakes use of WHILE so that nothing is done while the reversed condition is true. Once the reversed condition becomes true, then
WHENruns its instructions. Define the
WHENprocedure and then type:
CS HOME RIGHT 90 SETVELOCITY 100
The turtle is moving at a pretty good pace. If you wanted to turn it when it reached a specific x-coordinate, it might make a few passes around the screen before it makes the turn. In a case like this, it’s better to check for a range of x-coordinates. Type:
WHEN [AND .GE XCOR -5 .LE XCOR 5] [RIGHT 90]
The condition for the
WHEN procedure is testing the turtle’s
x-coordinate to see if it is in the range of -5 to +5. Notice that the
Red Traffic Light goes out because
WHEN stops after it runs its
UNLESS procedure is really the same thing as a
WHILE procedure, except that the condition is
UNLESS and then type:
CS RIGHT 90 UNLESS [XCOR > 100] [FORWARD 1]
The turtle draws a line of 101 steps. At that point, the
procedure stops because the x-coordinate is greater than 100. Type:
CS RIGHT 90 WHILE [NOT XCOR > 100] [FORWARD 1]
The same thing happens again because the reversed condition in the
WHILE command is really the same as the
condition for the
UNTIL procedure is very much like the
UNLESS procedure, except
that the instructions are always run at least one time - before the
condition is tested. Type:
CS RIGHT 90 FORWARD 101 GETXY Result: [101 0]
The turtle draws a line of 101 steps which makes its x-coordinate 101. Type:
UNTIL [XCOR > 100] [FORWARD 100]
UNTIL runs its instructions before it tests the condition, the
turtle draws another line of 100 steps. This does not happen with
UNLESS because the condition is tested before the instructions are
CS RIGHT 90 FORWARD 101 GETXY Result: [101 0] UNLESS [XCOR > 100] [FORWARD 100]
Since the x-coordinate is already greater than 100, the instruction list was not run.
Some people prefer to call the
DO.UNTIL to remind
them that the instructions are run at least one time. And, the
procedure is often called
UNTIL. While neither of these things is a
real problem, it would not be a good idea to have both
DO.UNTIL, unless you think you can remember which one does what when
A Logo Procedure
When you create a Logo procedure, you type in some text that Logo interprets to cause your computer to do something - like display a message, draw a line, make a sound or move a turtle. You may have thought that your “code” was somehow mysteriously transformed, internally, into something that only a computer could understand, but actually, Logo stores the text of your procedure in a format that looks very close to what you typed in.
DOUBLE procedure. It takes an input called
prints the value of
:INPUT multiplied by 2.
TO DOUBLE :INPUT PRINT :INPUT * 2 END
The TEXT command takes the name of a procedure as input and outputs the stored definition of the procedure. You might be surprised to see what a procedure looks like. Type:
TEXT "DOUBLE Result: [[:INPUT] [PRINT :INPUT * 2]]
Every procedure is just a list of lists. The first list contains the
names of the formal inputs. And, now you know why the Define a
Procedure window has the editbox titled “List of inputs.” Each
instruction line of a procedure is stored in its own list. You can
simulate what Logo does when it runs
DOUBLE. First, Logo finds the
list of inputs by getting the FIRST
element of the definition. Type:
FIRST TEXT "DOUBLE Result: [:INPUT]
In this example, the list of inputs has only one element. Logo finds the formal input name by getting the FIRST element of the list of inputs. Type:
FIRST FIRST TEXT "DOUBLE Result: :INPUT
The character sequence
:INPUT must be replaced with the value of
:INPUT in each instruction line. Of course, when you are actually
DOUBLE procedure, you would just type something like
DOUBLE 100, where 100 is the actual input you want to use. Logo would
then create a local variable named
:INPUT and assign it the value of
- However, since this is just a simulation of what Logo does with a
procedure definition, enter the following command to simulate the
MAKE "INPUT 100
Logo finds the value of the local variable
:INPUT with the
EVAL command. EVAL
takes a list as input and returns a list with the variable names
replaced by their values. Type:
EVAL FIRST TEXT "DOUBLE Result: 
Since EVAL returns a list, Logo has to use FIRST to get the actual number. Type:
FIRST EVAL FIRST TEXT "DOUBLE Result: 100
Now, Logo has to replace the character sequence
:INPUT with the value
100 in the instruction line. SUBST is a
command that substitutes every occurrence of its first input with its
second input. Type:
SUBST ":INPUT 100 [PRINT :INPUT * 2] Result: [PRINT 100 * 2]
Of course, the instruction list
[PRINT :INPUT * 2] is something that
Logo has to extract from the procedure definition. The one instruction
DOUBLE is the FIRST item of
the BUTFIRST of the procedure
FIRST BUTFIRST TEXT "DOUBLE Result: [PRINT :INPUT * 2]
Now, Logo can do the following command (which must be typed all on one long line). The parentheses are not required by Logo, but they show you what is involved in each separate part of the information that Logo is using. Type:
SUBST (FIRST FIRST TEXT "DOUBLE) (FIRST EVAL FIRST TEXT "DOUBLE) (FIRST BUTFIRST TEXT "DOUBLE) Result: [PRINT 100 * 2]
Now, all it takes to run the resulting instruction is to give the list to RUN. Type:
RUN SUBST (FIRST FIRST TEXT "DOUBLE) (FIRST EVAL FIRST TEXT "DOUBLE) (FIRST BUTFIRST TEXT "DOUBLE) 200
Of course, with more than one formal input and more than one instruction line in a procedure definition, things get quite complicated in a hurry. But, the process that Logo goes through to read your command line, evaluate the procedures to run your request, and finally print the result, are fundamentally the same. It’s referred to as the READ-EVAL-PRINT loop. There is not always something to print as a result of your request, but Logo actually does print something - even if it’s just the carriage return to get the cursor to the next line of the Listener window. Most of your instructions cause something to happen other than printing. This “other something” is referred to as a “side effect” of the READ-EVAL-PRINT loop.
Defining a Procedure
You can define a procedure with the DEFINE command. It needs a name for the definition and a list which contains the list of inputs and the lists of instructions. Obviously, it’s more convenient to use the editor or the Define a Procedure window to define a procedure. However, DEFINE can be useful. If you build a list of inputs and lists of instructions and use DEFINE to give them a name, you can build procedures. In fact, you can define a procedure that defines procedures! After all, they’re just lists.
DEFINE.USER procedure asks for a user’s name. The
READ command outputs a word typed in from the
CHAR 34 command outputs the quotation mark. The
DEFINE command creates a new procedure with
the user’s name as the name of the procedure. The first
LIST command creates the list that contains
the procedure definition. The new procedure does not require any inputs,
so the empty list is used as the list of inputs. The second
LIST command creates the only instruction
line for the new procedure. Define the
DEFINE.USER procedure using
either the Define a Procedure window or the editor. (Don’t try this
with the DEFINE command.) Run
and enter your own name in response to the question. Here is an example:
DEFINE.USER WHAT IS YOUR NAME? BRION
A new procedure named
BRION is created. To see its definition, type:
TEXT "BRION [ [OUTPUT "BRION]]
Now, when Brion wants to use his name, he only has to type
“BRION. In your case, just type in your name as a command - it is one.
What’s the point of all this? Just playing around and having fun? If programming wasn’t fun, who would ever do it? Here’s a fun thing to do on April’s Fools Day - add a little noisy bug to one of your procedures
DEFINE.USERin this example. Type:
DEFINE "DEFINE.USER LPUT [PLAY "WASP] TEXT "DEFINE.USER
DEFINE.USER is run, it will not only create a new procedure,
but it will also play the Wasp waveform sound file. Run
again and use the word
STARJEWELS as the username this time. Type:
DEFINE.USER WHAT IS YOUR NAME? STARJEWELS
You could even define a procedure to “bug” another procedure. Define the
BUG takes the name of a procedure as input and adds the
instruction to the end of the list of instructions in the procedure. To
get rid of a bug, you have to debug your procedure. Define the
procedure - it gets rid of the
PLAY “WASP instruction. If you really
want some noise, you can bug every procedure in your workspace,
DEBUG, with one command line!
FOREACH PROCLIST [BUG "?]
PROCLIST outputs a list of the names of
all of your procedures in the workspace. Almost every procedure you run
after this will play the sound file. The reporters (or operations) that
use OUTPUT will probably not play the file
because the “bug” is the last line and OUTPUT
stops a procedure - Logo will never get to the last line.
and the procedure named after you will not make any noise.
The point of all of this is really to show you that a program is not
just a program - sometimes a program is the data for another program.
A handy utility to have around is a procedure that searches other
procedures for a word. Suppose you wanted to know which procedures have
the formal input
:PROCEDURE. You could load them into an editor and do
a search or use POTS and scan the title
lines. It could also be done with a procedure to do the searching for
you. Define the
FORMAL? procedure looks in the first element of each procedure’s
definition - the list of inputs - to see if a match is found. The
POTS command does not like a quoted word,
so the WORD command is used to output the
value of the substitution parameter without one. Don’t put a colon in
front of the name you want to find; it won’t work. Type:
FORMAL? "PROCEDURE TO DEBUG :PROCEDURE TO BUG :PROCEDURE
A word frequency chart is a list of words along with a count of how many times each word is used. In programming, a more useful list is one that goes a step further and shows the procedure name where each word is used. This is called a cross reference. It sounds like a very complicated thing to create, especially considering that a long list of words is almost useless unless it’s in alphabetical order.
Procedures that do sorting are quite interesting. However, the
CROSS.REFERENCE program takes advantage of the
ADDSORTED command to
alphabetize the list of words while adding them to a listbox control.
The words are formatted in such a way that the procedure name is part of
the word - it’s kind of a kludge, but it does the job. (A kludge is a
technical term for something that actually works, but is implemented in
an unorthodox manner such that most people cringe when they see it.)
FOREACH to print the name of each procedure
as it loops through them all. The procedure name and its definition are
CROSS.1 subprocedure has three recursive
calls in it - it’s the same pattern of recursion that was used in the
ANY.MEMBER? procedure from earlier. When
CROSS.1 finds a word, it
passes it on to
CROSS.ADD which combines the word with the current
procedure name to form a property name. When there is no property with a
given name, the GPROP command returns
the empty list instead of an error. That’s how
CROSS.ADD knows that it
has found a new word and must initialize the property value to 0. The
property value is a count of how many times the word is used. By
combining the word and the procedure name, the count reflects how many
times each word is used in each procedure.
The input to the
FILLBOX procedure is the list of property pairs
output by the PLIST command. The
FILLBOX is similar to the
PRINT.DOWN procedure, except
that it has to remove two elements from the input list each time because
they represent one property pair. Each word, which has already been
combined with the procedure name, is combined with the count before it
is added to the listbox. (Another kludge.)
Use the Logo editor to enter the procedures for the
program. Save it with the file name of
XREF. The commands at the end
of the file are outside of the procedure definitions. When the
XREF file is loaded into the workspace, these commands are run as if
you had typed them in the Listener window.
The BURY command makes the four procedure names invisible to certain Logo commands. For example, buried procedures do not show up in the list that the PROCLIST command outputs. You don’t need a cross reference of the cross reference program. (That sounds recursive.) In addition, buried procedures do not show up in the PRINTOUT commands and they are not saved as part of your workspace file.
CROSS.REFERENCE procedure is run next. The
command just makes sure that the DECLARE
command will work properly. The new listbox control is created, its size
is adjusted, and then
FILLBOX adds the words to it.
The organization of the cross reference workspace file is typical of programs that automatically start when they are loaded. You can do the same thing with any of your workspace files.