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