So Logo is 40 years old. I’ll take this as an opportunity to talk about Logo-the-language (as opposed to Logo-the-graphics or Logo-the-educational-experience). It’s a much better language than most people appreciate.
Logo is Lisp. It’s an old Lisp but it’s very Lisp. Let’s look at a classic example:
TO square :size
REPEAT 4 [FD :size RT 90]
END
This translates to something like this (in Scheme):
(define (square size)
(repeat 4 (lambda ()
(begin (fd size)
(rt 90)))))
Well, technically it translates to this:
(define (square size)
(repeat 4 '(fd size rt 90)))
That is, [...] is a quoted list; nothing inside it is evaluated. This is actually a lot like Tcl:
proc square {size} {
repeat 4 {
fd $size
rt 90
}
}
In Tcl {...} is just a string literal, but one without substitution, and where you can nest your braces. It’s a lot like a quoted list in Lisp, except where lists are the fundamental type in Lisp, strings are the fundamental type in Tcl.
To get an idea of how this works, here’s the implementation of repeat in these different languages:
TO repeat :count :block
IF :count > 0 [EVAL :block
REPEAT (:count - 1) :block]
END
(define (repeat count block)
(if (> count 0)
(begin (block) (repeat (- count 1) block))))
proc repeat {count block} {
while {$count > 0} {
uplevel 1 $block
set count [expr {$count - 1}]
}
}
Tcl doesn’t really encourage recursion, and it’s only partially encouraged in Logo — some implementations implement tail call optimization, but not all of them.
Scoping is an interesting difference here. In Scheme the scoping is lexical, which is the norm in modern languages. That is, any variables you refer to in a lambda (like size in the example) are bound according to where in the source you define that anonymous function. In Tcl the uplevel statement says to evaluate the block in the calling scope. In Logo it’s all implicit because Logo is dynamically scoped. That is, each function call inherits all the variables from the calling function. So you can write something like this:
TO house :size
square
FD :size
RT 60
triangle
END
TO square
REPEAT 4 [FD :size RT 90]
END
TO triangle
REPEAT 3 [FD :size RT 120]
END
Here :size is inherited in called functions. You can write some really bad code using dynamic scoping, with lots of magic side effects, but Logo isn’t meant for writing large programs so it usually doesn’t come up. A nice side effect of dynamic scoping is that EVAL works pretty well.
Another interesting aspect of Logo (and Tcl) is that it has very few special forms. In one of Logo’s more pure implementations the only special form is TO, and even that wouldn’t have to be a special form (there’s a function DEFINE that does the same thing). For instance, here’s how you set a variable:
MAKE "variable <value>
That is, you call the function MAKE, give it a variable name, and then a value. Some dialects allow MAKE :variable <value>, but some like UCBLogo actually interpret that to mean: set the variable named by :variable.
Another interesting aspect of Logo is how it
handles parenthesis. You’ll notice
there aren’t many. Lisp is known (and
oft criticized) for its parenthesis. Logo
shows they aren’t strictly required;
it
does this by using the
arity of functions
to inform parsing. An example:
SETXY ABS :x :y
The equivalent Scheme:
(setxy (abs x) y)
If you know that SETXY takes two arguments, and that ABS takes one argument, you can figure out how to parse the Logo. Comparing it to Forth is kind of interesting:
y @ x @ abs setyx
Reverse the order, replace @ with :, and you have Logo. @ and : also share a lot in common: :variable in Logo is syntactic sugar for THING "variable (symbols in Logo are spelled "symbol, like 'symbol in Scheme, #symbol in Smalltalk, or :symbol in Ruby). THING "variable tells Logo to look up the value associated with the given symbol. Similarly in Forth, y refers to an address, and @ says to lookup the value at that address.
But back to parsing – Forth is based on trust and foreknowledge. You know that abs pops one thing from the stack, and setyx pops two things from the stack. You trust those words to act that way, because there’s nothing stopping them from popping more or less from the stack than they claim. Logo isn’t quite as trusting, but it does see that SETXY takes two values, and grabs two values. In the same way in the first example, when it sees [FD :size RT 90], it can tell that it’s two commands — newlines aren’t required.
One awkward part of this lack of parenthesis is that without knowing what functions you are referring to the expressions can’t be trully parsed. As a result most Logos are really interpreters. If anyone cared enough of course you could optimize this considerably, and maybe some of the more performant Logos like StarLogo or NetLogo do, but I don’t really know. PyLogo is pretty naive about it, and it’s not that fast as a result (but not terrible).
And of course I should take a chance to plug PyLogo, which runs Logo from Python, and lets Logo code easily call Python, and vice versa. Just easy_install PyLogo and you can run pylogo --console to amuse yourself with the language (the docs from UCBLogo might be helpful, and PROCEDURES shows all the functions, while HELP "IFELSE will show the help for a particular command.
Another related language is Rebol, which is very close to Logo without the turtles (though with a bunch of new literals and object-oriented features as well). Many things called Logo are just turtle graphics with an extremely poor "language" bolted on in front. Don’t be fooled! On the web Turtle Tracks is one of the more true implementations (though I can’t get graphics, hm).
No related posts.
Logo was the second language I used. I remember taking a class in Logo in 8th grade, and my final project was a turtle riding a half-pipe doing tricks. I was really into skateboarding at the time. Logo rocks! And I think I used an Apple IIe too.
Logo was my first introduction to computer programming.
There is so much to say. I wish I had paid more attention.
This is from a guy who saw his first computer in 83 and then bought an Apple IIgs because color monitors were cool in 86.
Now I am a damn .NET developer because that’s where the jobs are in my area.
I’m paraphrasing, but I believe Philip Greenspun once described Tcl as essentially begin a brain-dead version of Lisp.
Thanks for giving Logo some love, and mentioning NetLogo. (I am the lead developer of NetLogo.)
NetLogo is an unusual Logo; much of what you say about Logo in this post doesn’t apply to NetLogo. The differences are summarized in our FAQ at http://ccl.northwestern.edu/netlogo/docs/faq.html#logodiffs . In particular, NetLogo is lexically scoped and control structures are special forms. This brings us closer to Scheme, though at present we don’t have a macro facility or any other way for users to define their own special forms. These differences are there partly for design reasons and partly to facilitate efficient implementation.
NetLogo is partially compiled, partially interpreted. It’s reasonably fast — much faster than a naive, totally-interpreted implementation would be. All parsing is done at compile time. Our compiler generates Java byte code from snippets of user code, but it doesn’t handle the whole language yet, so we still have an interpreter to stitch those compiled snippets together. Over time, we plan to get closer and closer to full compilation.
Seth: I had a Logo compiler myself that did full compilation ([Logo in Scheme](http://www.colorstudy.com/static/ianb/old/logo-scheme/)). It didn’t do it lazily, which caused some problems; as a result you couldn’t use a function that had not been defined unless you used the explicit
(func arg1 arg2)
form, since it needed to look up the arity of the function. I suppose if it looked forTO
statements up-front (figuring out all the function signatures) this would also have fixed that problem.For blocks there was a special form like
TO repeat :count [block :expr]
, and similar to to arity the function definition would effect the parsing; lists in the:expr
position would actually be passed in as anonymous functions. Again, if you look up all the function definitions up-front this would work much better.In a [Tcl translator](http://www.colorstudy.com/static/ianb/old/tcl-scheme/) that I wrote it did the translation lazily (except in cases where it knew that a block was called for, like
proc
), and associated the translation with the string. This seems like a reasonable strategy for Logo as well. You could even compile all lists present in the source code, and simply ignore errors when they occur, and only actually signal the error at runtime if the person does use the list as a block.Another strategy, if you don’t mind diverging significantly from standard Logo (and NetLogo is already very divergent from the brief example you link to), is to do something more like Smalltalk, where code blocks have a convenient syntax. I believe Smalltalk actually took inspiration from Logo in this regard, using
[]
as a kind of lambda. You would need to get another syntax for literal lists then, but literal lists aren’t that interesting, and most other uses of lists in Logo tend to be substitutes for strings, e.g.,print [hello world!]
, and you’ve already got syntax for full strings in NetLogo instead of only words.And a [kind of version with SVG](http://www.fragmentarisch.net/svg/drawingboard.php)
Ugh… the SVG thing is nice in concept, but a horrible implementation of the Logo language! This is unfortunately very common. Which is a shame, because it’s really not much harder to implement the real Logo language than to implement these toy turtle graphics languages that people keep writing.
This link seems to be broken:
http://www.colorstudy.com/static/ianb/old/logo-scheme/
Is there another place I can get a copy of your scheme version of LOGO?
Thanks! -colleen
Note: archive.org still has it: http://web.archive.org/web/20071231105705/http://www.colorstudy.com/static/ianb/old/logo-scheme/