|

| |
Creating a Logo Tool Box
by
Brian Silverman and Michael Tempel |
| |
|
 |
© 1989 LCSI
© 1991 Logo Foundation
|
You may copy and distribute this document for educational purposes
provided that you do not charge for such copies and that this copyright
notice is reproduced in full.
|
Logo Foundation
250 West 85th Street, Suite 4D
New York, NY 10024
Telephone: (212) 579-8028
FAX: (212) 579-8013 Board of Directors
Seymour Papert, Chair
Clotilde Fonseca
Tessa R. Harvey
Geraldine Kozberg
Michael Tempel
Takayuki Tsuru The Logo Foundation is a nonprofit educational
organization incorporated in New York State.
|
It is often said that if Logo doesn't have a particular primitive
procedure you need, you can write it in Logo. Here are some tips
about how to do it.
Problem Solving vs Implementing Solutions
Most programming languages are designed for implementing the solutions
to problems. The ideology, if not always the practice, of many data
processing professionals is that problems should first be solved
in broad outline, then refined and only at the last step translated
into a program. Interaction with the computer does not aid in the
solution of the problem. The problem has presumably been solved
before coding begins.
Logo is ideally suited to a different sort of problem solving.
A problem may not be well defined, or exploration may begin without
a specific problem in mind. Logo provides an interactive environment
in which thinking may be expressed and reflected upon. Problems
are explored and solved while interacting with Logo.
Most programming languages are well suited to a particular class
of problems. This is quite reasonable when the problems are generally
familiar and predictable such as managing the company payroll or
processing tax returns. For example, Cobol makes it relatively easy
to format and display numerical information. It is good for business
applications. Fortran is designed for solving equations and is useful
to engineers. Each language contains the tools needed for its special
class of problems.
Logo is more general. If problems are loosely defined, you may
not know ahead of time what tools you will need. Therefore it's
best to have what you need to make tools as you go.
If you were messing around with probability and statistics, you
might want to have procedures that compute factorials, means,
medians and various kinds of random distributions. Logo gives
you the basic arithmetic operations and a simple random number generator.
You can use these to build what else you need.
If you were working with natural language, you might want procedures
that reverse lists, delete words from lists or insert words into
lists. The simple list manipulation procedures provided in Logo
may be used to create such tools.
Why not include all these various tools in Logo instead of asking
people to make them? For one thing, such an approach would produce
an enormous Logo. More important, since Logo is designed for exploration
and the creation of new knowledge, the designers of Logo can't presume
to know what tools will be needed by users. A well designed Logo
provides flexible general purpose building blocks.
Literacy vs Fluency
You might be feeling a bit uneasy about the preceding discussion.
You have been handed a Logo. Now get out there and explore, making
your tools as you go. Oh, by the way, you do, of course, know how
to write procedures like factorial, median, reverse,
delete and insert, don't you?
Well, if you don't, stick around for the rest of this workshop.
We will start by going through a series of procedures that illustrate
various points about Logo that are important for tool building.
Things generally get more difficult as we go.
The first step is to reach a level of literacy with these procedures.
That is, you should be able to follow how they work, not be surprised
at their results and even predict outcomes.
Beyond literacy you will achieve fluency, the ability to create
your own procedures of the kinds illustrated.
Procedures
Logo programs are built out of procedures. Some of these procedures
are primitives. You may also define your own procedures.
The only important difference between these two categories of procedures
is that the scripts of the primitives are written in machine language
while your procedures have Logo scripts. There are other, more important
distinctions between types of procedures.
Here are some procedures you are probably familiar with. They are
primitives. What categories can you divide them into?
forward
cleartext
print
word
heading
name
setpos
pos
home Here's one categorization:
|
forward
cleartext
setpos
name
print
home
|
word
heading
pos
|
|
|
|
The procedures in the first column are commands. The others
are reporters (also called operations). Commands do something.
reporters report, or output a Logo Thing. Logo Things may be words
or lists. When a procedure outputs a Logo Thing, there must be another
procedure there to accept it as an input. This leads us to a second
way to divide our list of procedures:
|
cleartext
home forward
name
print
setpos
|
heading
pos word
|
|
|
|
Cleartext and home are commands that do not take
any inputs. Forward, name, print and setpos
are commands that require inputs. If you type forward with
no number following it, Logo will complain. Forward 50 is
fine.
Heading and pos are reporters that don't require
inputs. Word needs two inputs.
Let's look at some interactions with Logo that show how all this
works:
|
?Cleartext
|
This command clears text
from the screen.
|
|
?forward
Not enough inputs
to forward
|
This command needs an input.
|
|
?forward 100
|
This works.
|
|
?word "Logo "Writer
I don't know what to
do with LogoWriter
|
Word is a reporter but
there's no procedure to
accept its output as an input.
|
|
?word 7 5
I don't know what to
do with 75
|
The same problem.
|
|
?forward word 7 5
|
The turtle moves
forward 75 steps.
|
You probably know how to write your own commands in Logo:
to square
repeat 4 [fd 50 rt 90]
end
Square is a command that takes no inputs. Here's a version
of square that takes one input:
to square :side
repeat 4 [fd :side rt 90]
end
In order to write a reporter in Logo you have to use the primitive
Output. Here's an example:
to five
output 5
end
?print five + five
10
Output takes a Logo Thing as an input. The number 5 is the
input to output. Output stops the procedure Five.
Output also causes Five to be a reporter whose output
is 5. In general, the input to output becomes the output
of the procedure. (Is that not less than unclear?)
Here's an example of a reporter that requires an input:
to double :number
output :number + :number
end
?print double 4
8
?print double five
10
Here's a procedure that pads one and two digit numbers to three
digits and leaves numbers with three or more digits alone.
to padnumber :num
if :num < 10 [output word "00 :num]
if :num < 100 [output word "0 :num]
output :num
end
Notice that if :num is less than 10, the second line is
never reached because output in the first line stops padnumber.
If :num is 10 or more but less than 100, padnumber
stops at the second line and the last line is not reached. If :num
is 100 or more the last line is executed.
Recursion
It turns out that many useful Logo reporters are recursive. Before
looking at some recursive reporters, here are some recursive commands:
to countdown :num
if :num < 1 [stop]
print :num
countdown :num - 1
end
to countdown :num
if :num < 1 [stop]
print :num
countdown :num - 1
print :num
end
Does the behavior of the second version surprise you? If so, here
are some things to think about:
- When a Logo procedure calls a subprocedure it waits until
the subprocedure finishes and then continues. A procedure doesn't
go away when it calls a subprocedure.
- Primitive procedures and user-defined procedures work the same
way.
- Procedures don't care what the names of their subprocedures
are.
- Subprocedures don't care about the names of the procedures that
call them.
Here's another pair of procedures that are equivalent to countdown:
to squiral :side
if :side > 100 [stop]
forward :side right 90
squiral :side + 5
end
to squiral :side
if :side > 100 [stop]
forward :side right 90
squiral :side + 5
left 90 back :side
end
Recursive Reporters Here's an example of a recursive reporter:
to factorial :number
if :number = 0 [output 1]
output :number * factorial :number - 1
end
?print factorial 0
1
?print factorial 5
120
This translates into English as: "The factorial of 0 is 1. The factorial
of a number greater than 0 is the number times the factorial of
one less than the number."
to sum.of :list
if empty? :list [output 0]
output (first :list) +
(sum.of butfirst :list)
end
?print sum.of [1 2
3 2 5]
13
In English this is: "If a list is empty, then the sum of the numbers
in it is 0. Otherwise, the sum of a list of numbers is the first
number plus the sum of the rest of the numbers in the list". Here's one more example:
to reverse :list
if equal? 1 count :list [output :list]
output lput first :list reverse bf :list
end
"The reverse of a list of one element is just that list. The reverse
of a longer list is the first element of the list stuck on the end
of the reverse of the rest of the list."
Here are some rules to observe when writing recursive reporters:
- There must be at least two different ways of handling the inputs,
a simple one that immediately produces an answer and another one
that, in part, uses the same procedure to produce the answer.
In factorial, an input of 0 produces 1 as an answer.
Otherwise factorial, in part, uses factorial
to compute an answer.
Both parts are needed. Try running this:
to factorial :number
output :number * factorial :number - 1
end
or this
to factorial :number
if :number = 0 [output 1]
end
- The input to the recursive subprocedure must be different from
the input to the procedure that called it. The input to the subprocedure
factorial is :number- 1. The input to the subprocedure
sum.of is butfirst :list. If the input to
the subprocedure were not different from the input to the calling
procedure, there would be no way for the simple case to ever be
reached. Try running each of these altered versions of factorial,
sum.of and reverse:
to sum.of :list
if empty? :list [output 0]
output (fisrt :list) + (sum.of :list)
end
to factorial :number
if :number = 0 [output 1]
output :number * factorial :number
end
to reverse :list
if equal? 1 count :list [output :list]
output lput first :list reverse :list
end
- All cases must result in something being output.
Here's a procedure that doesn't work:
to reverse :list
if equal? 1 count :list [output :list]
lput first :list reverse butfirst :list
end
Here's another flawed
procedure:
to sum.of :list
if empty? :list [stop]
output (first :list) +
(sum.of butfirst :list)
end
Look at the appendix for some more interesting examples of recursion.
Transportable Procedures
Good tools are useful in many contexts. You should be able to use
the same Logo procedure as a part of several programs. There may,
for example, be different situations in which you will need to find
the average of some numbers.
Here's one way of doing this:
to average :number.list
output (sum.of :number.list) /
(count :number.list)
end
?print average [1 2
3 4 5 6 7]
4
Here's another way:
to average
name [1 2 3 4 5 6 7] "numbers
name sum.of :numbers "total
name count :numbers "count
name :total / :count "average
end
?average
?print :average
4
The first version of average may be added into any Logo program
that doesn't already have a procedure called average. You
hand the procedure a list of numbers and you get back the average.
What goes on inside average does not affect names or other
procedures that may be around. It works with any list of numbers
as input.
The second version works with only a specific list of numbers.
It also uses the global names numbers, total, count and average.
If these names already exist, their values will be changed by the
procedure average.
In general, tools should be easily movable from one program to
another. In most cases, global names are to be avoided. The procedure
should not produce side effects upon the workspace or be inadvertently
affected by what is already around.
Projects
Here are some suggested projects on which to exercise your tool
building skills:
Games
Computers may be used to play games, such as chess or tic tac toe,
in several, increasingly complex ways:
- The computer displays the game board. It accepts moves that
are input by human players and simply shows the resulting positions.
- In addition to displaying moves, the computer knows what the
legal moves are and prevents illegal moves from being made.
- The program plays against a human. It makes legal moves, but
has no strategy for winning.
- The program has a playing strategy based on learning from previous
games. It has no initial game plan other than to make legal moves,
but it learns not to repeat fatal errors made in prior games.
- The program has a strategy built in from the start. This strategy
may be modified by experience.
The collection of procedures that you need at each step may be
incorporated into the program you create at the next level. Different
people may work on different aspects of the solution and mesh their
results together.
Here's a very simple game to try this on. It's called Hexapawn.
It is played on an abbreviated chess board that is three squares
on a side. Each player has three pawns. The opening position looks
like:

The legal moves in Hexapawn are the same as for the pawns in chess.
A pawn may move forward to the space in front of it if that space
is vacant. A pawn may capture an opposing piece by moving diagonally
forward. The game ends in one of three ways:
- If a player can make no legal moves his opponent wins.
- If a player captures all of his opponent's pieces he wins.
- If a player moves a piece to the opposite side of the board
he wins.
The game is limited enough to be of little interest to people,
but still presents a reasonable challenge for a computer.
After working with Hexapawn, you might make things more complicated
by playing Octopawn on a four by four board. Then try tic tac toe
and possibly chess.
When you get up to working on programming playing strategies you
should be aware of two general approaches to take. First, you can
program all possible game outcomes. If your program knows all of
the possibilities that can occur from a given point on, it also
knows which moves will win. It can always select the correct move
based on this knowledge. This works well for hexapawn and even tic
tac toe, but it would take the fastest computers available about
30 billion years to run through all the possible outcomes of a chess
game. Since you only have an Apple, it would take even longer. It
is this impracticality of an "exhaustive search" strategy that makes
chess playing programs interesting. Some rules and approaches need
to be programmed.
Graphs
Make a collection of tools that produce bar or line graphs from
some data. Some of the tools you would need would:
- draw a bar of specified size in a specified position
- plot points
- connect plotted points
- compute averages and totals
- draw axes and scale them properly
- put titles and labels on the graph
Pluralization
Write a program to pluralize any word you give it as input. It
should work like this:
?print plural "dog
dogs
?print plural "mouse
mice
?print plural "fox
foxes
As you work on this, you'll find that the Logo primitives first,
last, butfirst, word etc. may not always be
convenient to use directly. You might want to construct tools like
last.two or next.to.last. Other tools such as vowel?
might be useful.
Logo for Little Kids
Attempts have been made to make Logo accessible to very young children.
One category of solutions rests on the assumption that kids can't
find the Return key. These "Instant" type programs move the
turtle with single keystrokes. While easy to start using, these
programs are discontinuous with Logo as a whole since any Logo procedures
not assigned to keystrokes are unavailable, and the method of invoking
procedures - without pressing Return - will not apply when
the Instant program is discarded.
Can you create an Instant program that also allows the use of regular
Logo commands?
Instead of an Instant program, can you write a collection of tools
that simplify Logo in similar ways, but work at top level? For example,
write a procedure f that causes the turtle to go forward
10.
Try making a "slow turtle" whose moves and turns are slower than
normal so that the drawing process may be more easily followed.
Create a collection of procedures that scale the turtle movements
so that small numbers produce big effects.
You might also want to create tools like square, circle,
rectangle and triangle. Using these procedures children
can achieve satisfying results before they are able to program such
shapes themselves.
Appendix
Here are some interesting recursive Logo procedures:
to printvals :list
if empty? :list [stop]
printval first :list
printvals bf :list
end
to printval :var
type :var type char 32
if name? :var [pr thing :var][pr "\-\-\-]
end
?printvals [num size
foo]
will print the values of num, size and foo if they are names. If
they're not names, --- is printed.
to map :func :list
if empty? :list [stop]
run se :func [first :list]
map :func bf :list
end
to square :side
repeat 4 [fd :side rt 90]
end
?map "square [10 20
30 40 50]
displays five squares of the specified sizes.
to assign :names :vals
if empty? :names [stop]
make first :names first :vals
assign bf :names bf :vals
end
?assign [n1 n2 n3 n4]
[10 20 30 40]
?shownames
:n1 is 10
:n2 is 20
:n3 is 30
:n4 is 40
This procedure plays the Tower of Hanoi game:
to hanoi :from :to :spare
:n
if :n = 1 [(pr :from "to :to) stop]
hanoi :from :spare :to :n - 1
(pr :from "to :to)
hanoi :spare :to :from :n - 1
end
Try this:
?hanoi "A "B "C 1
A to B
?hanoi "A "B "C 2
A to C
A to B
C to B
?hanoi "A "B "C 3
A to B
A to C
B to C
A to B
C to A
C to B
A to B
?hanoi "A "B "C 10
to same.as? :a :b
if word? :a [output :a = :b]
if word? :b [output "false]
if subset? :a :b [output subset :b :a]
output "false
end
to subset? :a :b
if empty? :a [output "true]
if part.of? first :a :b [output subset? butfirst :a :b]
output "false
end
to part.of? :a :b
if empty? :b [output "false]
if same.as? :a first :b [output "true]
output part.of? :a butfirst :b
end
?print same.as? [a
b c] [b c a]
true
?print same.as? [a b c] [b c [a]]
false
?print same.as? [[a b c] d e] [e [c a b]
d]
true
?print subset? "a [a b c]
true
?print subset? [a] [a b c]
false
?print subset? [a] [[a][b][c]]
true
?print subset? [a b] [b a c]
true
?print part.of? "a [a b c]
true
?print part.of? [a][a b c]
false
|