Programming Language for Old Timers

by David A. Moon
February 2006 .. September 2008

Comments and criticisms to dave underscore moon atsign alum dot mit dot edu.

Previous page   Table of Contents   Next page


To define a class, you specify its name, supertypes, slots, options, initialization parameter list, initialization arguments for the superclass, and initialization expressions.

The class name prefixed with a '$' is defined in the current scope with a fixed definition as the class object. Unless the class is abstract or a different constructor name is specified, a constructor method definition for the class name with no '$' prefix is also created.

The supertypes of a class are one class, which defaults to anything, and zero or more protocols. Every member of a class is also a member of each of the supertypes. As a special case, the class anything has no superclass.

Of course a protocol can be added to a class at run time.

Slots define the structure of each instance of the class.

Each slot of a class has a name, which can be used on the right-hand side of the '.' operator that provides access to slots or can be invoked with ordinary function call syntax.

By convention a slot that should be considered private has a name that starts with underscore. PLOT does not define which methods if any are considered to be within the private domain and thus have the right to access private slots; that is up to the programmer. Thus privateness is enforced by social disapprobation, not by the compiler.

A class inherits all the slots of its superclass and cannot define any slot of its own with the same name in the same scope as any inherited slot.

Each slot of a class can be single-valued or multi-valued. Each value of a multi-valued slot is identified by a numeric subscript, starting from zero. The number of values of a multi-valued slot is fixed when an instance is created but can be different in each instance of a class. The number of values is specified as either a constant expression or this.slot where slot is a fixed, single-valued slot in the same class whose value is an integer.

Each slot of a class has an optional type restriction, which defaults to anything. Each value of a slot must be a member of that type.

Each slot of a class can be fixed or assignable. The value(s) of a fixed slot cannot be changed after an instance of the class has been initialized. The value(s) of an assignable slot can be changed using the assignment operator.

A single-valued slot name is defined as a function with a method with one parameter, whose type is the class, whose result is the value of the slot. This is called a reader method.

A multi-valued slot is defined as a function with two reader methods. The first reader method has one parameter, whose type is the class. It returns a non-assignable array whose elements are the values of the slot. The second reader method has two parameters, whose types are the class and integer. The second argument is the subscript. The reader returns the corresponding value, or signals an error if the subscript is out of bounds.

An assignable slot also has a writer method for a function whose name is the slot name suffixed with ":=". The writer method for a single-valued slot has two parameters; the first parameter's type is the class, the second parameter's type is the type of the slot. The slot's value is set to the second argument.

The writer method for a multi-valued slot takes a second argument, which is the subscript, and a third argument which is the new value. It signals an error if the subscript is out of bounds. There is no multi-valued slot writer method that accepts an array and writes all the values at once.

Reader and writer methods are always sealed, so they can be inlined when the object's class is statically known.

A slot must have an initialization expression if it is fixed, and can have an initialization expression if it is assignable. When an instance of the class is initialized the initialization expression is evaluated once. For a single-valued slot, the value of the slot is set to the result. For a multi-valued slot, the result of the initialization expression must be a sequence and the values of the slot are set to successive values of the sequence. An error is signalled if the sequence ends early. If the sequence is longer than the slot's number of values, extra sequence elements are ignored. The initialization expression sequence(value) is useful to initialize all values of a multi-valued slot to the same value.

If there is no initialization expression, the slot is assignable and an error is signalled if it is read before it is assigned.

The class anything acts as if it declares one slot, a fixed, single-valued slot named class with the type restriction class. This is the class of which an object is a direct instance. It is implementation-dependent whether there is actually a class slot with a single reader method, or several methods for the class function that get the class of different kinds of object.

The initialization parameter list of the class anything is empty.

A class can be abstract, which means it cannot have any direct instances. You can only create a direct instance of a non-abstract subclass. A common convention is to define a pseudo-constructor with the same name as an abstract class which creates an indirect instance of that class. The pseudo-constructor is not defined by defclass, you must define it separately.

To create a direct instance of a class, call the constructor function with arguments to which the constructor method is applicable. The parameter list of the constructor method is the class's initialization parameter list. The name of the constructor function is the name of the class without any $ prefix, unless a different name is specified using the constructor: class option. The constructor method determines the length of all multi-valued slots to determine the size needed, allocates storage, and then initializes the instance. Initialization initializes the highest superclass first and initializes the class being directly instantiated last. The initialization parameter list of a superclass receives arguments supplied by the subclass; these default to the arguments received by the subclass unless otherwise specified, with extra arguments being ignored if there are too many.

Initialization consists of evaluating the initial value expression for each slot and setting each value of the slot according to the result. Each value of a slot with no initial value expression is set to a special "uninitialized" flag value. After initializing the slots the initialization body is executed if present.

When an instance of a class is initialized, parameters defined by the class's initialization parameter list are in scope in the initialization expressions and in the initialization body. A variable named this is also in scope; its value is the instance being initialized.

Note that during initialization there can be uninitialized slots; it is an error to read the value of an uninitialized slot.

As a convenient abbreviation, the initialization parameter list and the superclass argument list can be omitted. In this case, the initialization parameter list defaults to the names of the slots restricted to the types of the slots, or to sequence for multi-valued slots, preceded by the superclass's initialization parameter list. Each slot is initialized to the value of the corresponding initialization parameter. The parameters at the beginning of the initialization parameter list, derived from the superclass's initialization parameter list, become the superclass argument list.

A class can be declared intrinsic, which means that the compiler has special knowledge of that class.

The syntax of the defclass statement is as follows:

defmacro defclass ?:name ^^
                  [ constructor: ?constructor is name ]
                  [ ?:input-parameter-list ]
                  [ ^^ ?=is { ^^ ?supertype is name [ ( ?:argument-list ) ] & , }+ ]
                  [ ?:annotations ]
                  { ^ ?:slot-declaration }*
                  [ ^ init: ?:block ]*
                  => ...

defparser slot-declaration ?:typed-variable [ \[ ?length \] ]
                           [ = ?value |
                             := ?initial-value ]
                           [ ?:annotations ]

The meaning of the various class options is:

constructor: gives the constructor function the specified name instead of the default name which is the class name without the "$" prefix.
input-parameter-list does double duty as the parameter list of the constructor method, if any, and as the parameters that are in scope in slot length, value, and initial-value expressions, in the initialization block, if any, and in the argument-list for superclass initialization, if any.
is specifies the name of the superclass and one or more protocols implemented by this class. All names here have the "$" prefix added to them.
argument-list is arguments to be passed to the superclass's initialization. If not specified, this defaults to the arguments passed to the subclass, with extra arguments being ignored if there are too many. An argument-list cannot be specified for a protocol.
init: gives a block of initialization expressions to be evaluated after initializing the superclass and the slots.

The following annotations are predefined for classes:
abstract indicates that the class cannot have direct instances and does not define a constructor method.
intrinsic(name) means this class is known specially to the compiler. The name is optional and defaults to the class name without the $ prefix.

The meaning of the various parts of a slot-declaration is:

typed-variable the slot name and optional type.
length the length expression for a multi-valued slot. It must be either a constant expression or this.slot where slot is a fixed, single-valued slot in the same class whose value is an integer. The value of this expression or the initialization expression of the slot is used for allocating storage. This expression is evaluated during bounds checking in the reader and writer methods.
value the initial value expression for a fixed slot.
initial-value the initial value expression for an assignable slot.

There are no predefined annotations for slot-declarations. Slot read and write methods are always sealed.

A class object remembers its name. When doing separate compilation, references to the class are written into the fasl file using this name. When the fasl file is loaded, the corresponding runtime class object can be located as the definition of this name.

class, the constructor function for the class class, takes arguments analogous to the parts of a defclass statement:

;; Construct a class
defun class(name is name,
            supertypes is collection,                  ; of types
            slots is collection,                       ; of slot-specifiers
            key: abstract is boolean,
                 init is expression or false,
                 intrinsic is name or false,
                 parameter-list is parameter-list or false,
                 superclass-arguments is argument-list or false)

;; Construct a slot-specifier
defun slot-specifier(name is name,
                     type is type,
                     key: assignable is boolean,
                          initial-value is expression or false,
                          length is expression or false)

The supertypes argument to the class constructor can contain at most one class. All other elements of the collection must be protocols. Ranges and type-unions are not allowed.

Note that the constructor for the class class and the reader for the class slot are methods for the same function. There is no ambiguity since the constructor requires at least three arguments and the reader accepts only one argument.

To allow for mutually referencing classes, the type slot of a slot-specifier is assignable. The compiler first creates all the class and slot-specifier objects, then fills in the slot-specifiers' types. Except for this deferred initialization, the type slot of a slot-specifier must not be changed.

The defuns of the constructor, readers, and writers are separate.

The constructor, if any, is defined after the class. It takes care of initializing the slots. The parameter-list, superclass-arguments, and init arguments to the class constructor are only remembered for use when defining subclasses. The initial-value and length arguments to the slot-specifier constructor are only remembered for use when defining subclasses. The assignable argument to the slot-specifier constructor is only remembered for reflection.

Type names are converted to type objects before run time. Thus when a development environment allows a user to redefine a type, either the existing type object must be modified to represent the new type, or the development environment must search memory for all references to the old type object and replace them with the new type object. Outside of development, types cannot be redefined.

A subclass of a class that has multiple-valued slots of non-constant length will generate more code when accessing its own slots since their offsets are variable.

Simple example (part of PLOT):

defclass ratio constructor: %ratio(n is integer, d is integer) is rational
  numerator   = n
  denominator = d

The above can be abbreviated to:

defclass ratio constructor: %ratio is rational
  numerator is integer
  denominator is integer
and the constructor function will be %ratio(numerator is integer, denominator is integer). The ratio pseudo-constructor reduces its arguments to lowest terms before calling %ratio or returning an integer result. The / method for integer arguments simply calls ratio.

Previous page   Table of Contents   Next page