The open model is more dynamic (CLOS, Meroon-V3, STklos). It acknowledges the fact that all behaviors cannot be expressed at class-definition time, that it should be possible to add new behaviors to existing instances (data, particularly if persistent, often pre-exist to treatments), that introspection is desirable and useful for serialization, debug and all sorts of walkers, that some way to customize or extend the implementation should also exist as well.
This document proposes an open model in the dynamic spirit of Scheme (and Lisp). Meta-object aspects are not considered in this proposal but are still possible.
This model makes possible that vectors be regular instances of the object system since the object system allows fields to be regular (ie Mono-Field (a Mono-Field only holds one value)) or indexed (ie Poly-Field, the number of values it may hold is fixed at instantiation-time). Vectors are regular objects with a single indexed field.
The proposed model is based on simple inheritance (for the same reasons Java took).
The following are new special-forms. They use keywords ie symbols prefixed with a colon.
(define-class class-name superclass-name (field-specification ...) ;; where field-specification is ;; ([ = | * ] field-name ;; [ :mutable | :immutable | ;; :initialize (lambda () form) | ; for (=) Mono-Field ;; :initialize (lambda (index) form) ; for (*) Poly-Field ;; ] ) [ :prototype ] ) ; non-instantiable class (define-generic (generic-name variable ...) ;; where variable is ;; symbol ; regular variable ;; | (symbol [ class-name ]) ; discriminating variable [ default-body ] ) (define-method (generic-name variable ...) ;; where variable is ;; symbol ; regular variable ;; | (symbol class-name) ; discriminating variable body ; (call-next-method) allowed ) (instantiate class-name :mono-field-name form :poly-field-name form ... :poly-field-length size ... )
A define-class form creates a new class (redefinition of classes is left unspecified). The first parameter is the name of the class, the second is the name of the super-class, specifications of fields appear as third parameter, additional parameters may appear after. Except initializers that are evaluated, no parameter or expressions within parameters are evaluated.
A class is made of fields which may be regular or indexed. A field may be mutable or not (mutable by default). A default initializer may be specified (a thunk for a regular field, a unary function for an indexed field). The form that define these initializers are evaluated at class-definition time. The :prototype class option forbids instantiation of the class.
When a class is defined, some functions are created: a predicate and some read/write accessors.
The following class definition creates the following functions: Foo?, Foo-bar, Foo-hux, set-Foo-hux! and Foo-hux-length. A variable Foo-class is also created that holds a (possibly indirect) instance of Class for introspection purpose.
(define-class Foo Object ((= bar :immutable) (* hux :initializer (lambda (i) i)) ) )
Here is the signature of the generated functions:
(Foo? anySchemeValue) -> boolean (Foo-bar aFooInstance) -> anySchemeValue (Foo-hux aFooInstance aNaturalNumber) -> anySchemeValue (set-Foo-hux! aFooInstance anySchemeValue aNaturalNumber) -> unspecified (Foo-hux-length aFooInstance) -> aNaturalNumber
Predicates, such as Foo? may be invoked on any Scheme values. Accessors may only apply on (possibly indirect) instances of Foo. Writers return an unspecified value. Accessors whether readers or writers when applied on an indexed field take an index as last argument.
The define-generic form defines generic functions. When a generic function is defined, it does not possess any method. However it may have a default body which is invoked when no specific method is found (for instance, on values that are not objects). The generic function must specify the discriminating variables (at least one). Syntactically a discriminating variable appears within parentheses. If a class is associated to a discriminating variable, it restricts the set of methods that may be adjoined to this generic function (for instance, the following clone function may only have methods defined on subclasses of Object). A generic function may have more than one discriminating variable.
The following are predefined generic functions. New methods may be added at will by programmers.
(define-generic (initialize! (o Object)) ...) (define-generic (clone (o Object)) ...)
The initialize! function is automatically invoked on every freshly created instance. It may be used to register the instance in some data structures.
The clone function, by default, performs a shallow copy of an object.
A generic predicate werk? may be defined on all (direct or indirect) instances of Object as:
(define-generic (werk? (o)) #f )
The define-method form adjoins a method to a generic functions. Methods may be adjoined to any generic functions provided that the method has a compatible signature ie the same number of discriminating variables at the same position associated to classes that are sub-classes of the classes mentioned in the generic function definition. If the generic function has a final dotted variable, then methods should also have such a final dotted variable.
Within the body of a method, it is possible to use the (call-next-method) local special form. It is not possible to use it elsewhere. The next method is defined to be the method that would have been invoked if the current method was absent. It is forbidden to define ambiguous multi-method that is a method that will make some signature ambiguous. This problem may only arise on methods that have strictly more than one discriminating variable: is SC is a subclass of C and a method M1 is defined on SC * C then it is not possible to define a method M2 on C * SC since that would make a call on SC * SC ambiguous. However if a method M3 on SC * SC is defined and does not contain a call to (call-next-method) then it is then possible to define a method M2 on C * SC. [NOTE: that may be considered as too dynamic, observe however that call-next-method is not a variable.]
If all (direct or indirect) instances of Foo satisfy the werk? predicate, then one may write:
(define-method (werk? (o Foo)) #t )
New objects may be created with the instantiate special form. This form takes as first parameter the name of a class. The other parameters specify the initial content of the object. The initial content of a field is specified by a keyword whose name is the name of the field to fill (prefixed with a colon). If the field is regular, the keyword is followed by a single expression (to be evaluated); if the field is indexed, the keyword is followed by as many expressions (to be evaluated) as needed. Alternatively, an indexed field may be specified by its length in which case the initial content of this indexed field is unspecified.
The fields do not need to be ordered nor to be all specified. In which case, the initializer if defined at class-definition-time is run. For a regular field, the initializer looks like a thunk; for an indexed field, the initializer is a unary function taking the index as argument. Fields are guaranteed to be filled in order starting from inherited fields. Indexed fields are filled starting from 0.
Here are some instantiations of Foo:
(instantiate Foo :bar 1 :hux 2 3 4) (instantiate Foo :hux-length 3 :bar 11)Note that the values of the fields may be given in any order. A mono-field defined with an initializer may be omitted. A poly-field defined with an initializer only requires its length to be given.
The following are regular functions. Note: value means whatever Scheme value; object means a (possibly indirect) instance of Object; class means a (possibly indirect) instance of Class.
(object? value) ; anySchemeValue -> boolean (object->class object) ; Object -> Class (is-a? value class) ; anySchemeValue * Class -> boolean (subclass? class super-class) ; Class * Class -> boolean
Among meta-objects only classes and fields are partially exposed not by their definition but through some functions or some variables. Class is the ``class of classes'' where classes are the values returned by the object->class function. The document does not impose Class to be definable by a specific define-class form. Classes may only be used with the functions whose name start with Class-.
Field is the class of fields, objects reifying information about fields. The document does not impose Field to be definable by a specific define-class form. Fields may only be used with the functions whose name start with Field-.
The definition of a class may be inspected via a number of functions. It is possible to access the name of a class, its superclass (#f if that does not exist) as well as its fields. Fields are reified as if instances of a class of fields: Field. The document does not impose Field to exist (as if explicitly created by define-class) provided the Field-* functions still exist.
Object-class ; Class (Class? value) ; anySchemeValue -> boolean (Class-name class) ; Class -> symbol (Class-super-class class) ; Class -> Class (Class-field class index) ; Class * nat -> Field (Field? value) ; anySchemeValue -> boolean (Field-name field) ; Field -> symbol (Field-mutable? field) ; Field -> boolean (Field-initializer field) ; Field -> closure (Mono-Field? value) ; anySchemeValue -> boolean (Poly-Field? value) ; anySchemeValue -> boolean (field-value object field [ index ]) ; Object * Field [* nat] -> value (set-field-value! object value field [ index ]) ; Object * value * Field [* nat] -> unspecified (field-length object field) ; Object * Field -> nat
Two generic functions are predefined:
(initialize! object) ; Object -> Object (clone object) ; Object -> Object
Copyright (C) Christian Queinnec (2000). All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.