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.
<object> |
<character> |
<condition> See table 2 |
<function> |
?? |
<simple-function> |
<generic-function> |
<simple-generic-function> |
<collection> |
<sequence> |
<list> |
<cons> |
<null> |
<character-sequence> |
<string> |
<vector> |
<table> |
<hash-table> |
<lock> |
<number> |
<integer> |
<fpi> |
<float> |
<double-float> |
<stream> |
<buffered-stream> |
<string-stream> |
<file-stream> |
<name> |
<symbol> |
<keyword> |
<thread> |
<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.
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>.
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.
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:
The class precedence list controls system-defined protocols concerning:
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}* |
A symbol naming a binding to be initialized with the new structure class. The binding is immutable.
A symbol naming a binding of a class to be used as the direct superclass of the new structure class.
Either a slot-name or a list of slot-name followed by some slot-options.
A key and a value (see below) which, taken together, apply to the class as a whole.
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:
The class-options are interpreted as follows:
The class of all functions.
Place holder for <simple-function> class.
The class of all generic functions.
Place holder for <simple-generic-function> class..
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 |
One of a symbol, or a form denoting a setter function or a converter function.
The parameter list of the generic function, which may be specialized to restrict the domain of methods to be attached to the generic function.
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.
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:
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.
≡ |
≡ |
≡ |
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>.
defmethod-form: |
( defmethod gf-locator |
specialized-lambda-list |
body ) |
gf-locator: |
identifier |
( setter identifier ) |
( converter identifier ) |
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>).
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.
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.
The result of calling the next most specific applicable method.
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.
(next-method?) → boolean |
If there is a next most specific method, next-method? returns a non-() value, otherwise, it returns ().
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
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 a1…am[am+1…an] 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 a1…am[am+1…an] iff there exists an i ∈ (1…m) so that D1i is more specific than D2i with respect to Ci, the class of ai, and for all j = 1…i - 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 a1…am[am+1…an]):
The first two steps are usually called method lookup and the first four are usually called generic dispatch.
Objects can be created by calling
The class of the object to create.
Initialization arguments.
An instance of class.
The general constructor make creates a new object calling allocate and initializes it by calling initialize. make returns whatever allocate returns as its result.
The class to allocate.
The list of initialization arguments.
A new uninitialized direct instance of the first argument.
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.
The object to initialize.
The list of initialization arguments.
The initialized object.
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.
The object to initialize.
The list of initialization arguments.
The initialized object.
This is the default method attached to initialize. This method performs the following steps:
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.
The class of all “names”.