11 Objects

In EULISP, every object in the system has a specific class. Classes themselves are first-class objects. In this respect EULISP differs from statically-typed object-oriented languages such as C++ and μ CEYX. The EULISP object system is called TELOS. The facilities of the object system are split across the two levels of the definition. Level-0 supports the definition of generic functions, methods and structures. The defined name of this module is telos0.

Programs written using TELOS typically involve the design of a class hierarchy, where each class represents a category of entities in the problem domain, and a protocol, which defines the operations on the objects in the problem domain.

A class defines the structure and behaviour of its instances. Structure is the information contained in the class’s instances and behaviour is the way in which the instances are treated by the protocol defined for them.

The components of an object are called its slots. Each slot of an object is defined by its class.

A protocol defines the operations which can be applied to instances of a set of classes. This protocol is typically defined in terms of a set of generic functions, which are functions whose application behaviour depends on the classes of the arguments. The particular class-specific behaviour is partitioned into separate units called methods. A method is not a function itself, but is a closed expression which is a component of a generic function.

Generic functions replace the send construct found in many object-oriented languages. In contrast to sending a message to a particular object, which it must know how to handle, the method executed by a generic function is determined by all of its arguments. Methods which specialize on more than one of their arguments are called multi-methods.

Inheritance is provided through classes. Slots and methods defined for a class will also be defined for its subclasses but a subclass may specialize them. In practice, this means that an instance of a class will contain all the slots defined directly in the class as well as all of those defined in the class’s superclasses. In addition, a method specialized on a particular class will be applicable to direct and indirect instances of this class. The inheritance rules, the applicability of methods and the generic dispatch are described in detail later in this section.

Classes are defined using the defclass (11.3.1) and defcondition (13.0.1) defining forms, both of which create top-lexical bindings.

Generic functions are defined using the defgeneric defining form,which creates a named generic function in the top-lexical environment of the module in which it appears and generic-lambda, which creates an anonymous generic function. These forms are described in detail later in this section.

Methods can either be defined at the same time as the generic function, or else defined separately using the defmethod syntax operator, which adds a new method to an existing generic function. This syntax operator is described in detail later in this section.

11.1 System Defined Classes

The basic classes of EULISP are elements of the object system class hierarchy, which is shown in table 1.


Table 1: Level-0 class hierarchy
A <object>
C <character>
A <condition> See table 2
A <function>
C ??
C <simple-function>
P <generic-function>
C <simple-generic-function>
A <collection>
A <sequence>
P <list>
C <cons>
C <null>
A <character-sequence>
C <string>
C <vector>
A <table>
C <hash-table>
C <lock>
A <number>
A <integer>
C <fpi>
A <float>
C <double-float>
A <stream>
A <buffered-stream>
C <string-stream>
C <file-stream>
A <name>
C <symbol>
C <keyword>
A <thread>
C <simple-thread>

Indentation indicates a subclass relationship to the class under which the line has been indented, for example, <condition> is a subclass of <object>. The names given here correspond to the bindings of names to classes as they are exported from the level-0 modules. Classes directly relevant to the object system are described in this section while others are described in corresponding sections, e.g. <condition> is described in § 12.8. In this definition, unless otherwise specified, classes declared to be subclasses of other classes may be indirect subclasses. Classes not declared to be in a subclass relationship are disjoint. Furthermore, unless otherwise specified, all objects declared to be of a certain class may be indirect instances of that class.


11.1.1 <object>
class


The root of the inheritance hierarchy. <object> defines the basic methods for initialization and external representation of objects. No initialization options are specified for <object>.


11.1.2 <class>
class


The default super-class including for itself. All classes defined using the defclass form are direct or indirect subclasses of <class>. Thus, this class is specializable by user-defined classes at level-0.

11.2 Single Inheritance

TELOS level-0 provides only single inheritance, meaning that a class can have exactly one direct superclass—but indefinitely many direct subclasses. In fact, all classes in the level-0 class inheritance tree have exactly one direct superclass except the root class <object> which has no direct superclass.

Each class has a class precedence list (CPL), a linearized list of all its superclasses, which defines the classes from which the class inherits structure and behaviour. For single inheritance classes, this list is defined recursively as follows:

  1. the CPL of <object> is a list of one element containing <object> itself;
  2. the CPL of any other class is a list of classes beginning with the class itself followed by the elements of the CPL of its direct superclass which is <object> by default.

The class precedence list controls system-defined protocols concerning:

  1. inheritance of slot and class options when initializing a class;
  2. method lookup and generic dispatch when applying a generic function.

11.3 Defining Classes


11.3.1 defclass
defining operator


11.3.1.1 Syntax

defclass-form:
( defclass class-name superclass-name
( slot* ) class-option* )
class-name:
identifier
superclass-name:
identifier
slot:
slot-name
( slot-name slot-option* )
slot-name:
identifier
slot-option:
keyword: identifier
default: level-0-form
reader: identifier
writer: identifier
accessor: identifier
required?: boolean
class-option:
keywords: ( identifier* )
constructor: constructor-specification
predicate: identifier
abstract?: boolean
constructor-specification:
( identifier identifier* )
initlist:
{identifier object}*
Arguments

class-name :

A symbol naming a binding to be initialized with the new structure class. The binding is immutable.

superclass-name :

A symbol naming a binding of a class to be used as the direct superclass of the new structure class.

slot :

Either a slot-name or a list of slot-name followed by some slot-options.

class-option :

A key and a value (see below) which, taken together, apply to the class as a whole.

Remarks

defclass defines a new structure class. Structure classes support single inheritance as described above. Neither class redefinition nor changing the class of an instance is supported by structure classes 1 1 .

The slot-options are interpreted as follows:

keyword : identifier :

The value of this option is an identifier naming a symbol, which is the name of an argument to be supplied in the initialization options of a call to make on the new class. The value of this argument in the call to make is the initial value of the slot. This option must only be specified once for a particular slot. The same keyword name may be used for several slots, in which case they will share the same initial value if the keyword is given to make. Subclasses inherit the keyword. Each slot must have at most one keyword including the inherited one. That means, a subclass can not shadow or add a new keyword, if a superclass has already defined one.

default : level-0-form :

The value of this option is a form, which is evaluated as the default value of the slot, to be used if no keyword is defined for the slot or given to a call to make. The expression is evaluated in the lexical environment of the call to defclass and the dynamic environment of the call to make. The expression is evaluated each time make is called and the default value is called for. The order of evaluation of the defaults in all the slots is determined by initialize. This option must only be specified once for a particular slot. Subclasses inherit the default. However, a more specific form may be specified in a subclass, which will shadow the inherited one.

reader : identifier :

The value is the identifier of the variable to which the reader function will be bound. The binding is immutable. The reader function is a means to access the slot. The reader function is a function of one argument, which should be an instance of the new class. No writer function is automatically bound with this option. This option can be specified more than once for a slot, creating several bindings for the same reader function. It is a violation to specify the same reader, writer, or accessor name for two different slots.

writer : identifier :

The value is the identifier of the variable to which the writer function will be bound. The binding is immutable. The writer function is a means to change the slot value. The creation of the writer is analogous to that of the reader function. The writer function is a function of two arguments, the first should be an instance of the new class and the second can be any new value for the slot. This option can be specified more than once for a slot. It is a violation to specify the same reader, writer, or accessor name for two different slots.

accessor : identifier :

The value is the identifier of the variable to which the reader function will be bound. In addition, the use of this slot-option causes the writer function to be associated to the reader via the setter mechanism. This option can be specified more than once for a slot. It is a violation to specify the same reader, writer, or accessor name for two different slots.

required? : boolean :

The value is either t or (). t indicates that an initialization argument must be supplied for this slot.

The class-options are interpreted as follows:

keywords : ( identifier* ) :

The value of this option is a list of identifiers naming symbols, which extend the inherited names of arguments to be supplied to make on the new class. Keywords are inherited by union. The values of all legal arguments in the call to make are the initial values of corresponding slots if they name a slot keyword or are ignored by the default initialize <object> method, otherwise. This option must only be specified once for a class.

constructor : constructor-specification :

Creates a constructor function for the new class. The constructor specification gives the name to which the constructor function will be bound, followed by a sequence of legal keywords for the class. The new function creates an instance of the class and fills in the slots according to the match between the specified keywords and the given arguments to the constructor function. This option may be specified any number of times for a class.

predicate : identifier :

Creates a function which tests whether an object is an instance of the new class. The predicate specification gives the name to which the predicate function will be bound. This option may be specified any number of times for a class.

abstract? : boolean :

The value is either t or (). t indicates that the class being defined is abstract.


11.3.2 abstract-class?
function


11.3.2.1 Signature

(abstract-class? object) <object>
Arguments

object :

Returns object, if it is an abstract class, otherwise ().

NOTE 1 abstract-class? is not currently implemented in Youtoo.

11.4 Defining Generic Functions and Methods


11.4.1 <function>
<object>  class


The class of all functions.


11.4.2 <simple-function>
<function>  class


Place holder for <simple-function> class.


11.4.3 <generic-function>
<function>  class


The class of all generic functions.


11.4.4 <simple-generic-function>
<generic-function>  class


Place holder for <simple-generic-function> class..


11.4.5 defgeneric
defining operator


11.4.5.1 Syntax

defgeneric-form:
( defgeneric gf-name gf-lambda-list
level-0-init-option )
gf-name:
identifier
gf-lambda-list:
specialized-lambda-list
level-0-init-option:
method method-description
method-description:
( specialized-lambda-list form* )
specialized-lambda-list:
( specialized-parameter+ {. identifier} opt )
specialized-parameter:
( identifier class-name )
identifier
Arguments

gf-name :

One of a symbol, or a form denoting a setter function or a converter function.

gf-lambda-list :

The parameter list of the generic function, which may be specialized to restrict the domain of methods to be attached to the generic function.

level-0-init-option :

is method method-description
where method-description is a list comprising the specialized-lambda-list of the method, which denotes the domain, and a sequence of forms, denoting the method body. The method body is closed in the lexical environment in which the generic function definition appears. This option may be specified more than once.

Remarks

This defining form defines a new generic function. The resulting generic function will be bound to gf-name. The second argument is the formal parameter list. The method’s specialized lamba list must be congruent to that of the generic function. Two lambda lists are said to be congruent iff:

  1. both have the same number of formal parameters, and
  2. if one lambda list has a rest formal parameter then the other lambda list has a rest formal parameter too, and vice versa.

An error is signalled (condition class: <non-congruent-lambda-lists> ) if any method defined on this generic function does not have a lambda list congruent to that of the generic function.

An error is signalled (condition class: <incompatible-method-domain> ) if the method’s specialized lambda list widens the domain of the generic function. In other words, the lambda lists of all methods must specialize on subclasses of the classes in the lambda list of the generic function.

An error is signalled (condition class: <method-domain-clash> ) if any methods defined on this generic function have the same domain. These conditions apply both to methods defined at the same time as the generic function and to any methods added subsequently by defmethod. An level-0-init-option is an identifier followed by a corresponding value.

An error is signalled (condition class: <no-applicable-method> ) if an attempt is made to apply a generic function which has no applicable methods for the classes of the arguments supplied.

11.4.5.2 Rewrite Rules
     
     
     
Examples

In the following example of the use of defgeneric a generic function named gf-0 is defined with three methods attached to it. The domain of gf-0 is constrained to be <object> × <class-a>. In consequence, each method added to the generic function, both here and later (by defmethod), must have a domain which is a subclass of <object> × <class-a>, which is to say that <class-c>, <class-e> and <class-g> must all be subclasses of <class-a>.

(defgeneric gf-0 (arg1 (arg2 <class-a>))
  method (((m1-arg1 <class-b>)
           (m1-arg2 <class-c>)) ...)
  method (((m2-arg1 <class-d>)
           (m2-arg2 <class-e>)) ...)
  method (((m3-arg1 <class-f>)
           (m3-arg2 <class-g>)) ...))

See also

defmethod, generic-lambda.


11.4.6 defmethod
defining operator


11.4.6.1 Syntax

defmethod-form:
( defmethod gf-locator
specialized-lambda-list
body )
gf-locator:
identifier
( setter identifier )
( converter identifier )
Remarks

This syntax operator is used for defining new methods on generic functions. A new method object is defined with the specified body and with the domain given by the specialized-lambda-list. This method is added to the generic function bound to gf-name, which is an identifier, or a form denoting a setter function or a converter function. If the specialized-lambda-list is not congruent with that of the generic function, an error is signalled (condition class: <non-congruent-lambda-lists> ). An error is signalled (condition class: <incompatible-method-domain> ) if the method’s specialized lambda list would widen the domain of the generic function. If there is a method with the same domain already defined on this gneric function, an error is signalled (condition class: <method-domain-clash>).


11.4.7 generic-lambda
special operator


11.4.7.1 Syntax

generic-lambda-form:
( generic-lambda gf-lambda-list
level-0-init-option* )
Remarks

generic-lambda creates and returns an anonymous generic function that can be applied immediately, much like the normal lambda. The gf-lambda-list and the level-0-init-options are interpreted exactly as for the level-0 definition of defgeneric.

Examples

In the following example an anonymous version of gf-0 (see defgeneric above) is defined. In all other respects the resulting object is the same as gf-0.

(generic-lambda ((arg1 <object>)
                 (arg2 <class-a>))
  method (((m1-arg1 <class-b>)
           (m1-arg2 <class-c>)) ...)
  method (((m2-arg1 <class-d>)
           (m2-arg2 <class-e>)) ...)
  method (((m3-arg1 <class-f>)
           (m3-arg2 <class-g>)) ...))

See also

defgeneric.

11.5 Specializing Methods

The following two operators are used to specialize more general methods. The more specialized method can do some additional computation before calling these operators and can then carry out further computation before returning. It is an error to use either of these operators outside a method body. Argument bindings inside methods are immutable. Therefore an argument inside a method retains its specialized class throughout the processing of the method.


11.5.1 call-next-method
special operator


11.5.1.1 Signature

(call-next-method) <object>
Result

The result of calling the next most specific applicable method.

Remarks

The next most specific applicable method is called with the same arguments as the current method. An error is signalled (condition class: <no-next-method>) if there is no next most specific method.


11.5.2 next-method?
special operator


11.5.2.1 Signature

(next-method?) boolean
Result

If there is a next most specific method, next-method? returns a non-() value, otherwise, it returns ().

11.6 Method Lookup and Generic Dispatch

The system defined method lookup and generic function dispatch is purely class based.

The application behaviour of a generic function can be described in terms of method lookup and generic dispatch. The method lookup determines

  1. which methods attached to the generic function are applicable to the supplied arguments, and
  2. the linear order of the applicable methods with respect to classes of the arguments and the argument precedence order.

A class C1 is called more specific than class C2 with respect to C3 iff C1 appears before C2 in the class precedence list (CPL) of C3 2 2.

Two additional concepts are needed to explain the processes of method lookup and generic dispatch: (i) whether a method is applicable, (ii) how specific it is in relation to the other applicable methods. The definitions of each of these terms is now given.

A method with the domain D1 ××Dm[×<list>] is applicable to the arguments a1am[am+1an] if the class of each argument, Ci, is a subclass of Di, which is to say, Di is a member of Ci’s class precedence list.

A method M1 with the domain D11 × × D1m[×<list>] is more specific than a method M2 with the domain D21 × × D2m[× <list>] with respect to the arguments a1am[am+1an] iff there exists an i (1m) so that D1i is more specific than D2i with respect to Ci, the class of ai, and for all j = 1i - 1, D2j is not more specific than D1j with respect to Cj, the class of aj.

Now, with the above definitions, we can describe the application behaviour of a generic function (f a1am[am+1an]):

  1. Select the methods applicable to a1am[am+1an] from all methods attached to f.
  2. Sort the applicable methods M1Mk into decreasing order of specificity using left to right argument precedence order to resolve otherwise equally specific methods.
  3. If call-next-method appears in one of the method bodies, make the sorted list of applicable methods available for it.
  4. Apply the most specific method on a1am[am+1an].
  5. Return the result of the previous step.

The first two steps are usually called method lookup and the first four are usually called generic dispatch.

11.7 Creating and Initializing Objects

Objects can be created by calling


11.7.1 make
function


Arguments

class :

The class of the object to create.

key1 obj1 ... keyn objn :

Initialization arguments.

Result

An instance of class.

Remarks

The general constructor make creates a new object calling allocate and initializes it by calling initialize. make returns whatever allocate returns as its result.


11.7.2 allocate
function


Arguments

class :

The class to allocate.

initlist :

The list of initialization arguments.

Result

A new uninitialized direct instance of the first argument.

Remarks

The class must be a structure class, the initlist is ignored. The behaviour of allocate is extended at level-1 for classes not accessible at level-0. The level-0 behaviour is not affected by the level-1 extension.


11.7.3 initialize
generic function


Generic Arguments

object <object> :

The object to initialize.

initlist :

The list of initialization arguments.

Result

The initialized object.

Remarks

Initializes an object and returns the initialized object as the result. It is called by make on a new uninitialized object created by calling allocate.

Users may extend initialize by defining methods specializing on newly defined classes, which are structure classes at level-0.


11.7.4 initialize <object>
method


Specialized Arguments

object <object> :

The object to initialize.

initlist :

The list of initialization arguments.

Result

The initialized object.

Remarks

This is the default method attached to initialize. This method performs the following steps:

  1. Checks if the supplied keywords are legal and signals an error otherwise. Legal keywords are those specified in the class definition directly or inherited from a superclass. An keyword may be specified as a slot-option or as a class-option.
  2. Initializes the slots of the object according to the keyword, if supplied, or according to the most specific default, if specified. Otherwise, the slot remains “unbound”.

Legal keywords which do not initialize a slot are ignored by the default initialize <object> method. More specific methods may handle these keywords and call the default method by calling call-next-method.

11.8 Accessing Slots

Object components (slots) can be accessed using reader and writer functions (accessors) only. For system defined object classes there are predefined readers and writers. Some of the writers are accessible using the setter function. If there is no writer for a slot, its value cannot be changed. When users define new classes, they can specify which readers and writers should be accessible in a module and by which binding. Accessor bindings are not exported automatically when a class (binding) is exported. They can only be exported explicitly.

11.9 Other Abstract Classes


11.9.1 <name>
<object>  class


The class of all “names”.

See also

<symbol> and <keyword>.