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


Defclass

To define a class, you specify its name, supertypes, slots, options, initialization parameter list, 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.

Each slot of a class has a name, which identifies the slot on the right-hand side of the '.' operator that provides access to slots. Slot names are absolute particles; they are independent of definitions and modules.

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; 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 as any inherited slot.

Each slot of a class can be real or virtual. Real slots define the structure of each instance of the class. Virtual slots have computed values when they are read and computed side-effects when they are written, but do not occupy any space in instances of the class.

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 real multi-valued slot is fixed when an instance is created but can be different in each instance of a class.

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 virtual slot has a read body, a write body, or both. The read body is executed when the virtual slot is read and returns the result of reading the slot. A parameter named this is in scope in the read body; its value is the instance whose slot is being read. If the slot is multi-valued, a subscript parameter named i is in scope in the read body. If there is no read body the slot cannot be read.

The write body of a virtual slot is executed when the slot is written and performs the effect of writing the slot. A parameter named this is in scope in the write body; its value is the instance whose slot is being written. A parameter whose name is the slot name is in scope in the write body; its value is the value being written into the slot. If the slot is multi-valued, a subscript parameter named i is in scope in the write body. If there is no write body the virtual slot is fixed and cannot be assigned.

A multi-valued virtual slot can also have an array body. If present, the array body is executed when the slot is referenced without a subscript. A parameter named this is in scope in the array body; its value is the instance whose slot is being read or written. The array body should return an object that implements the array protocol in a way that is consistent with the read and write bodies. This can be a freshly created object each time the array body is executed.

Each multi-valued real slot has an associated real, fixed, single-valued length slot which contains a non-negative integer, the number of values of the multi-valued slot in this instance. The length slot is initialized to the value of a length expression when an instance of the class is initialized and cannot be changed thereafter. The compiler can optimize out the storage for the length slot if the length is known to be the same for all instances of a class.

Each multi-valued virtual slot has an associated virtual, fixed, single-valued length slot which contains a non-negative integer, the number of values of the multi-valued slot in this instance.

---TBD: Is there an upper bound on the value of a length slot, even when it is virtual? This can facilitate some compiler optimizations

A real 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 and each value of the slot is set to the result. For a multi-valued slot, the initialization expression is evaluated again for each value of the slot, and the name i has a visible definition as the numeric subscript. If there is no initial value expression, the slot is assignable and an error is signalled if it is read before it is assigned.

The class anything 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. Whether this slot is real or virtual is implementation-dependent. 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 evaluates the length expressions of all real 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 real slot and setting each value of the slot to the result. Each value of a multi-valued real slot is initialized by a separate evaluation of the initial value expression. Each value of a real slot with no initial value expression is set to a special "uninitialized" flag value. After initializing the real 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 length expressions of real slots and in the initialization body if present. A parameter named this is also in scope; its value is the instance being initialized. During evaluation of the initial value expression for a multi-valued slot the name i is also in scope; its value is the numeric subscript of the value being initialized.

Note that during initialization there can be uninitialized slots; it is an error to read or write the value of an uninitialized real 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, 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
                  [ ??abstract abstract: | constructor: ?constructor is name ]
                  [ ( ?:parameter-list ) ]
                  [ ?=is { ?supertype is name [ ( ?:argument-list ) ] & ~^ , }+ ]
                  { ^ ?:slot-declaration }*
                  [ ^ intrinsic: ?intrinsic is name | ^ init: ?:block ]*
                  => ...

defparser slot-declaration ?:typed-variable [ \[ ?length \] ]
                           [ = ?value |
                             := ?initial-value |
                             [ read:  ?read is block |
                               write: ?write is block |
                               array: ?array is block ]+ ]

The meaning of the various class options is:

abstract: indicates that the class cannot have direct instances and does not define a constructor method.
constructor: gives the constructor function the specified name instead of the default name which is the class name without the "$" prefix.
parameter-list does double duty as the parameter list of the constructor method, if any, and as the parameters that are in scope in real 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.
intrinsic: means this class is known specially to the compiler.
init: gives a block of initialization expressions to be evaluated after initializing the superclass and the slots.

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. If the slot is real, this is evaluated when an instance is created. If the slot is virtual, this is evaluated each time the length slot is read.
value the initial value expression for a real, fixed slot.
initial-value the initial value expression for a real, assignable slot.
read the read block of a virtual slot.
write the write block of a virtual slot.
array the block for accessing the entirety of a multi-valued virtual slot as an object that implements the array protocol.

The syntax for accessing a slot is:

defoperator .
  infix-macro: ?:name [ \[ ?subscript \] | . length ] => ...

The left-hand side is the object whose slot is being accessed. The compile-time type of this object must have a slot with the specified name.

The optional parts following the slot name must only be present if the slot is multi-valued. A subscript expression enclosed in brackets selects the value to be accessed. The absolute particle length preceded by a dot selects the length slot.

A slot access can be the left-hand side of an assignment (:=) operator in which case the slot is written.

If the optional parts following the name of a multi-valued slot are omitted, the result is an object that implements the array protocol (and thus also implements the collection and sequence protocols) and shares storage with the slot. If the slot is assignable, the object implements assignable-array. This can be a freshly created object each time the slot is accessed this way. This feature is only implemented automatically for real slots. A virtual slot can implement this using the array: keyword.

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
def 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
def slot-specifier(name is name,
                   type is type or function,
                   key: assignable is boolean,
                        initial-value is expression or false,
                        real-length is expression or false,
                        virtual is boolean,
                        virtual-array is function or false,
                        virtual-length is function or false,
                        virtual-read is function or false,
                        virtual-write is function 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.

When the type of a slot-specifier is a function, it is a thunk encapsulating a forward reference to a type defined later. The first time the type is needed, the function is called with no arguments and the slot remembers the result as its type. The thunk is usually called at most once although this is not guaranteed.

The constructor, if any, is created separately. 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 real-length arguments to the slot-specifier constructor are only remembered for use when defining subclasses. The assignable, virtual-length, virtual-array, virtual-read, and virtual-write arguments to the slot-specifier constructor are only remembered for use when compiling slot references.

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(n is integer, d is integer) is rational
  numerator   = n
  denominator = d

The above can be abbreviated to:

defclass ratio is rational
  numerator is integer
  denominator is integer
and the constructor function will be ratio(numerator is integer, denominator is integer).


Previous page   Table of Contents   Next page