Pegasus 2000 - NeoLogo

email Jecel Mattos de Assumpção Jr for more information

Logo was created by Daniel Bobrow and Wallace Feurzeig (Bolt, Beranek and Newman, Inc.) with Seymour Papert (Massachusetts Institute of Technology) in the 1960s. Its motto was "low floor, high ceiling", which means that it should be as easy as possible for a total novice to start getting some results (low floor) but that there should be no limits to what an advanced user can do (the high ceiling part). While it did rather well in its first two decades, the simple line drawings ("turtle graphics") for which it is mostly remembered don't seem so relevant as we enter the twenty first century. Weak implementations of the language have made the ceiling much lower than it should have been. Modern versions include multimedia and hypertext in an attempt to bring Logo into the 90s, but that just increases the number of "magical things" that the kids can't do using the language itself (in other words - pretty results, a lot less learning).

Alan Kay's Smalltalk project at Xerox PARC was greatly influenced by Logo initially, but quickly evolved along a very separate path. Recent versions are very complex development environments used by trained professionals to develop mission critical applications at large corporations. In an attempt to bring back "the power of simplicity", Self was designed by David Ungar (Stanford) and Randall Smith (Xerox PARC) and later developed by them at Sun Microsystems.

NeoLogo was created by Jecel Assumpcao Jr. in 1994 to bring children the power of Self in the familiar form of Logo.

Objects

NeoLogo is a pure object oriented programming language based on prototypes. This means that everything is an object in it and that each object is a complete entity (it doesn't need helper objects like "classes"). The first available prototype based language was Object Logo, but objects were an add-on feature and not part of that language's basic design.

Like in Logo or Lisp, the most fundamental object in NeoLogo is the list. An example would be:

That is a list with three elements: a number, another list and a word. There are commands to take lists apart and to put them together. These same commands also work for numbers (which can be thought of as a list of decimal digits) and words (which can be viewed as a list of characters, except that in NeoLogo characters are represented by words with size 1 like in Logo and Self). So while it wouldn't be totally correct to say that there are nothing but lists in NeoLogo, it is close enough for all practical purposes.

If everything is a list, not all lists are created equal. Each list has an invisible tag which defines its type: quoted list (like the example), subexpression, number, word or general object. A subexpression is just like a quoted list, but is delimited by parenthesis instead of square brackets and instead of returning itself when evaluated (like all the other types do) it returns the result of running the code it contains. So we can write:

and see the number 8 appear on the screen (the outer parenthesis are implied and could have been omitted with exactly the same results). This:

would make a list with three elements appear on the screen, while this:

would make the system complain that it doesn't know what to do with the list you gave it which has two elements (inside a quoted list, there is no real difference between another quoted list and a subexpression).

The last type of list is the general object. They replace the association lists of Lisp or the property lists of Logo. In theory, we could write an object like this:

where (....) represents some code that we don't want to show right now. This represent a point object (the canonical example in all object oriented languages) which has 5 "slots", each with a name and a value. The odd items in the list are the slot names and the even items are the slot values. The first two slots (with the names "x" and "y") are called data slots since their values are simple NeoLogo objects, and the other three slots are called method slots since their values are code objects. The problem with doing it this way is that the system wouldn't know that we meant this to be an general object instead of a quoted list, so we will write it using curly brackets instead of square ones as delimiters (this kind of thing is needed because list type information can't be directly manipulated).

Note that like the other kinds of lists, objects can be shared:

where "make" is used to assign a value to a global variable and "sentence" is used to combine two lists into a single one (it makes a copy of its first list argument, but shares the second list argument - a very important detail for this example). Now we have two different points (called "a" and "b") which have their own "x" and "y" values but share the same three method slots. If any change is made to this shared part, both points will be affected at the same time. Note that slot names are searched for from the front of the list to the back, so if the same name appears more than once the first slot will override (or "shadow") all the later ones. This is like inheritance in most object oriented languages, but much simpler.

In a prototype based language, one of the most common operations is making a copy of some object ("cloning" it). This means that the above scheme isn't very convenient: a copy will take up a lot of space since it will also have to point to the slot names and it will get a separate copy of what should have been shared information (a cloned point will no longer be affected when the method "+" is changed for the prototipical point, for example). We solve both problems by actually structuring our "a" point object like this:

We can still write and print object "a" like we did before - this is just an internal implementation detail which only the search algorithm has to worry about. But now the clone operation can make a copy of just the first three elements of this list/object and share the rest (including the list of slot names - it is also shared. This didn't save much here, but can make a big difference for larger objects).

Syntax

We have been using Logo syntax in all the examples so far. That is possible because while the syntax in NeoLogo is much simpler, it is fully compatible with Logo programs. It does allow, however, many different expressions which would cause errors if typed in a Logo interpreter (this increase in expressive power makes catching errors in a way that makes sense to the users a little harder than in Logo).

A NeoLogo expression always looks like this (or is a literal object like we have seen above):

<receiver> SELECTOR <arg1> <arg2> <arg3>....

where the selector is always a word object and the arguments are other expressions (the number of arguments will vary depending on the selector and can even be zero). If the receiver is not present or if it is indicated by a ":" character, then it is assumed to be the currently executing context (which is an object like any other). Otherwise it can be any valid NeoLogo expression. When this expression is evaluated, we say that we are sending the message "selector" to the object "receiver" (or to "self" if there is no explicit receiver). This means we ask the search algorithm for a slot named "selector" in the object "receiver" and the result is either the associated value directly or, if that value is a code object, the result of executing that code.

Note that we have given ":" a very different meaning than it has in Logo, yet you can still type in programs you find in any Logo book and have the same results in NeoLogo:

Language: Logo NeoLogo
syntax: :traitsPoint :traitsPoint
alternate syntax (thing "traitsPoint) (traitsPoint)
meaning: returns the value of the variable "traitsPoint" as defined in the currectly executing context sends the message "traitsPoint" to the currently executing context. This will search for a slot of that name and return the corresponding value

The problem with this syntax is that it is highly ambiguous. Should this expression:

be interpreted as:

or:

or even as:

(after all, it might be perfectly valid to send the message "*" to the current context)? The first thing to notice is that in all cases the "2" is part of the first argument of the "+" message. In this case, this is also the last argument (since "+" only takes one argument). If the "+" message had required more arguments, would could have discarded the first interpretation (and the third too, but then this fourth alternative would have become possible ".... multiArgSelector 2 ( : * ...." so that doesn't help much). The solution adopted for NeoLogo isn't very elegant: the selectors are looked up in a table which tells us which interpretation is the right one. This table only contains the selectors that exist in Logo, and is set up to give the same results as the most popular implementations of that language (some, like Texas Instruments Logo, gave very different results and cause a lot of frustration among users). For any selectors not predefined in the table, the default interpretation is always the second one above - any others can be obtained with explicit parenthesis.

Scoping

One source of variation between Lisp dialects (including Logo but not Scheme) is the issue of dynamic scoping versus lexical scoping. If you refer to a variable that is not defined in the local code you are executing (as an argument name, for example), then should we look for this variable in the code that called us or in the code that defined us? Since Logo doesn't allow a procedure to be defined inside another procedure, this second alternative becomes "all variables not defined locally are global". Both styles have been implemented for Logo, and programs have been written which depend on one or the other, so there are no easy solutions.

The rule in NeoLogo is simple: messages to the current context are dynamically scoped, while messages with explicit receivers are not. The exact same execution mechanism takes care of both cases:

After the newContext is created, the dummy values that it initially contains are replaced by the results of evaluating the remaining expressions in the original context - this is how the formal parameters and the actual parameters get associated. "result" in the above code is a template with the argument names (slots with the dummy values) and newContext is a copy of that template but connected to the receiver of the message (so any slot of the receiver is also a slot of newContext). If "receiver" was a regular object, we start a new chain of contexts rooted in that object (and through it, normally, to the "global variables") which is very much lexically scoped Logos. If "receiver" was already a context (the message send was to an implicit receiver) then we will be extending that context with new names and will have access to all names defined locally in that context - this is exactly like dynamically scoped Logos.

More Logo compatibility

It could hardly be considered a Logo without turtle graphics, right? So we have a turtle object (which can be cloned to make as many as we want) that knows how to make line drawings on the screen. In addition, it also include a series of slots to make the purely object oriented NeoLogo look like the functional Logo. Here is a very object oriented NeoLogo expression:

but we can type this to get the same result:

since this means (in NeoLogo):

and the turtle object defined slots "if" and "print" in terms of "ifTrue" and "printOn". So all the old Logo programs still work, but they aren't as fast as they could be since they involve an extra layer of indirection through the turtle object (you send a message to the turtle and all it does is send another message to the argument you passed it).


back to Pegasus 2000 home page