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