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 very 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.
More Loops
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 WHEN
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 THEN
and 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
GO, but 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.
The WHEN
procedure is only going to run its instruction list one time
- not really a loop. However, the looping is done while
WHEN
is waiting for the condition to become true. TheWHEN
procedure is like the opposite of WHILE. For that reason, the:CONDITION
that is input toWHEN
is reversed with the NOT command.WHEN
makes use of WHILE so that nothing is done while the reversed condition is true. Once the reversed condition becomes true, thenWHEN
runs its instructions. Define theWHEN
procedure 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
instructions.
The UNLESS
procedure is really the same thing as a
WHILE procedure, except that the condition is
reversed. Define UNLESS
and then type:
CS RIGHT 90
UNLESS [XCOR > 100] [FORWARD 1]
The turtle draws a line of 101 steps. At that point, the UNLESS
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 UNLESS
procedure.
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]
Because 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
run. Type:
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 UNTIL
procedure DO.UNTIL
to remind
them that the instructions are run at least one time. And, the UNLESS
procedure is often called UNTIL
. While neither of these things is a
real problem, it would not be a good idea to have both UNTIL
and
DO.UNTIL
, unless you think you can remember which one does what when
forever.
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.
Define the DOUBLE
procedure. It takes an input called :INPUT
and
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
running the 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
creation of
:INPUT
. Type:
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: [100]
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
line for DOUBLE
is the FIRST item of
the BUTFIRST of the procedure
definiton. Type:
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.
The DEFINE.USER
procedure asks for a user’s name. The
READ command outputs a word typed in from the
keyboard. 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 DEFINE.USER
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
, not
“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.USER
in this example. Type:
DEFINE "DEFINE.USER LPUT [PLAY "WASP] TEXT "DEFINE.USER
Whenever DEFINE.USER
is run, it will not only create a new procedure,
but it will also play the Wasp waveform sound file. Run DEFINE.USER
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
procedure.
BUG
takes the name of a procedure as input and adds the PLAY “WASP
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 DEBUG
procedure - it gets rid of the PLAY “WASP
instruction. If you really
want some noise, you can bug every procedure in your workspace,
including BUG
and 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. STARJEWELS
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.
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.)
The superprocedure, CROSS.REFERENCE
, uses
FOREACH to print the name of each procedure
as it loops through them all. The procedure name and its definition are
passed to CROSS.1
. The 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
recursion in 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 CROSS.REFERENCE
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.
The CROSS.REFERENCE
procedure is run next. The ERASE CROSSREFERENCE
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.