User Tools

Site Tools


logo:tutorial:chapter11

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.

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. The WHEN procedure is like the opposite of WHILE. For that reason, the :CONDITION that is input to WHEN 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, then WHEN runs its instructions. Define the WHEN 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 100. 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.

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