Prototyped Objects In Logo

The following is a description of an object-oriented extension to the Logo programming language that I designed, implemented and documented, all in about 24 hours on a long busy day in 1986. Maybe noticeable as a candidate for setting a record for implementing an object-oriented language, but really more a measure of why fully dynamic programming languages are good to work with.

The Logo implementation of which this is a part ran under MS-DOS 3.1. The implementation itself isn't of much interest any more -- advances in operating systems have made it obsolete -- but there are things about it that are still of interest, including this feature. The object implementation includes a pretty thorough collection of operations that can be made available using prototyped objects. The Logo implementation of which it's part had features such as multiple threads (about 1000 simultaneous threads was a practical limit), a recursive call depth limit of about 4000 calls, multiple windows, support for multiple monitors, and multiple turtles, all running on a 4.77MHz PC XT machine!

For those of you not familiar with the Logo programming language, it might help to note that it's really a dialect of Lisp, using a simple "prefix" syntax that allows most of the parentheses to be omitted.

I've not changed the text other than removing formatting whitespace.

METHODS

In Logo any symbol can have one or more properties associated with it.
Any symbol can be given a property with any name.  Every property of a
symbol has a value associated with it.  As well as allowing the user to
use properties to represent various organizations of data, Logo itself
uses the PROCEDURE, VALUE, PACKAGE and PRIMITIVE properties of symbols
to implement the corresponding features of the language.

This implementation of Logo contains a set of primitives that extend its
capabilities to support object-oriented programming.  Objects are named
by Logo symbols or names.  More than one procedure or "method" can be
associated with a given object, and an object can "inherit" methods and
properties from one or more other objects, allowing objects to be
grouped into a set of hierarchies of classes.

A method is defined using the same "TO ...  END" format as a procedure,
except that both a method and object name are given:

TO method-name $ object-name :first-parameter ...

The method is then called with:

method-name $ object-name method-arguments ...

where "method-name $ object-name" replaces the procedure name in the
more usual formal of the call.  Unlike a procedure call, the method-name
and object-name are first evaluated to produce the names and can
therefore be any word-returning expression.  Because a normal procedure
is a method with a method-name of "PROCEDURE",

FN "X

and

"PROCEDURE $ "FN "X

do the same thing, except that if the object "FN" did not have the
"PROCEDURE" property but did inherit it (see below), the second example
would still work, whereas the first would produce an error.  Just as a
procedure with a given name is the "PROCEDURE" property of that name, a
method is a property with the method-name as its property name.

An object can have a list of other objects attached to it, from which it
inherits any methods it does not itself define.  If an object doesn't
define a method that is used, the object from which it inherits is
examined for that method.  If that object in turn does not define the
method, then then its inheritance is searched, and so on.  If an object
inherits from more than one other object, then each of the objects from
which it inherits is examined in turn.

To define the inheritance of an object use the INHERIT primitive,
defined below.  In keeping with the use of property lists, INHERIT makes
the list of objects from which an object inherits the "INHERIT" property
of that object's name.

To allow property values to be inherited in the same way as methods, the
FIELD primitive is provided, which acts like GPROP except that if the
given object does not have the given property, then its inheritance is
searched, as if it were a method.

The usual property manipulation primitives can be used to work on
methods and fields, but the following additional primitives make the job
a lot easier.

CLASS -> word
            To be used inside the definition of a method.  It returns the
            object that actually had the current method attached as a
            property.  This will be the same as the value returned by "SELF"
            (below) if the method did not need to be inherited.

CLASSOF word word-or-list -> word
            Returns the object from which the object or list of objects
            given as the second argument would inherit the property (either
            a method or field name) given as the first argument.

FIELD word word-or-list -> value
            Returns the value of the property whose name is given by the
            first argument as inherited from the object or inheritance list
            given by the second argument.  Unlike "GPROP", "FIELD" produces
            an error if the given property can not be found.

FIELDP word word-or-list -> boolean
            Returns whether there is any property whose name is given by the
            first argument to be inherited from the object or inheritance
            list given by the second argument.

METHOD -> word
            To be used inside the definition of a method.  It returns the
            name of the method being run.  For a normal procedure,
            "PROCEDURE is returned.

METHODP word word-or-list -> boolean
            Returns whether there is any property whose name is given by the
            first argument to be inherited from the object or inheritance
            list given by the second argument, and in addition whether that
            property's value is a validly defined method.

SELF -> word
            To be used inside the definition of a method.  It returns the
            name of the object by which the currently running method was
            inherited.  For a normal procedure, the name of the procedure is
            returned.  "SELF" is very useful as the last argument of many of
            the other primitives defined here.

.METHOD -> word
            Returns the "method-name" of the last method or procedure
            defined by a "TO ... END".  The method-name of a normal
            procedure is "PROCEDURE".

In addition to being able to put "method-name $ object-name" in the
place of a procedure name in a call, three primitives can be put in that
place.

THISMETHOD method-arguments ...
            THISMETHOD calls the currently running method or procedure with
            the given arguments.  It allows recursive calls to be coded no
            matter how a definition is copied between objects or from where
            it is inherited.

USUAL method-arguments ...
            USUAL calls the method or procedure with the same name as the
            currently running one, but taken from the inheritance of the
            class in which the currently running method was found as a
            property.  It allows a method to add its own actions to those
            that would have been taken had it not been defined and a further
            level of classes been inherited from.

QUA word word word-or-list method-arguments ...
            QUA calls the method name given as the second argument as
            inherited from the object or list given as the third argument
            but using the first argument as "SELF" when the method is
            actually called.  It allows a method to be "borrowed" even
            though it would not normally be inherited.  It can also be used
            to access methods that would normally be hidden by lower members
            in the inheritance hierarchy.

USUALP -> boolean
            Returns whether there is a "USUAL" method to be run.

The highly uniform manner in which Logo data and procedures are
represented prevents the obvious addition of one major (and very
desirable) feature of object-oriented programming: information hiding.
The user is not restricted as to how the internal structure of an object
can be manipulated.

It should be noted that as with many other facilities in Logo, all but
two of the above primitives can be defined in terms of others.  The
alternative forms of procedure call listed above can all be defined in
terms of QUA:

method-name $ object-name  is equivalent to
                           QUA object-name method-name object-name
THISMETHOD                 is equivalent to
                           QUA SELF METHOD SELF
USUAL                      is equivalent to
                           QUA SELF METHOD GPROP CLASSOF METHOD SELF "INHERIT

The other primitives (except for .METHOD) can be defined as follows:

TO CLASS
OUTPUT ITEM 3 .TRACE .LEVEL - 1
END

TO CLASSOF :FIELD :OBJECT
IF WORDP :OBJECT [OUTPUT IF PROPP :OBJECT :FIELD ~
                          [:OBJECT] [CLASSOF :FIELD GPROP :OBJECT "INHERIT]]
IF FIELDP :FIELD FIRST :OBJECT [OUTPUT CLASSOF :FIELD FIRST :OBJECT]
OUTPUT CLASSOF :FIELD BUTFIRST :OBJECT
END

TO FIELD :FIELD :OBJECT
OUTPUT GPROP CLASSOF :FIELD :OBJECT :FIELD
END

TO FIELDP :FIELD :OBJECT
IF WORDP :OBJECT [OUTPUT IF PROPP :OBJECT :FIELD ~
                             ["TRUE] [FIELDP :FIELD GPROP :OBJECT "INHERIT]]
IF :OBJECT = [] [OUTPUT "FALSE]
IF FIELDP :FIELD FIRST :OBJECT [OUTPUT "TRUE]
OUTPUT FIELDP :FIELD BUTFIRST :OBJECT
END

TO METHOD
OUTPUT ITEM 2 .TRACE .LEVEL - 1
END

TO METHODP :METHOD :OBJECT
IF NOT FIELDP :METHOD :OBJECT [OUTPUT "FALSE]
LOCAL "TEXT
MAKE "TEXT FIELD :METHOD :OBJECT
IF OR NOT LISTP :TEXT :TEXT = [] [OUTPUT "FALSE]
LOCAL "N
MAKE "N 1
REPEAT COUNT :TEXT [(IF NOT LISTP ITEM :N :TEXT [OUTPUT "FALSE]) ~
                        MAKE "N :N + 1]
LOCAL "FORMALS
MAKE "FORMALS FIRST :TEXT
MAKE "N 1
REPEAT COUNT :TEXT [(IF NOT WORDP ITEM :N :FORMALS [OUTPUT :FALSE]) ~
                        MAKE "N :N + 1]
OUTPUT "TRUE
END

TO SELF
OUTPUT FIRST .TRACE .LEVEL - 1
END

TO USUALP
OUTPUT FIELDP METHOD GPROP CLASSOF METHOD SELF "INHERIT
END

All the primitives used here are standard in Logo except for .TRACE and
.LEVEL, which allow the user to examine the current execution stack, and
PROPP, which returns whether or not a symbol has a given property.  The
only way in which these procedure definitions will differ from the
primitives of the same name is in the error message that will result
when, say, FIELD "X "Y is called and Y doesn't have a field called X.

© copyright 2004 by Sam Wilmott, All Rights Reserved

22 August 2004