This module is the core object system. It is a heavily hacked version
of the original Tiny-CLOS code from Xerox, but it has been fitted to
MzScheme, optimized and extended. See the source file for a lot of
details about how the CLOS magic is created.
[There is one difference between Swindle and Tiny-CLOS: the meta object
hierarchy is assumed to be using only single inheritance, or if there is
multiple inheritance then the built in meta objects should come first to
make the slots allocated in the same place. This should not be a
problem in realistic situations.]
This operation changes the class of the given `object' to the given
`new-class'. The way this is done is by creating a fresh instance of
`new-class', then copying all slot values from `object' to the new
instance for all shared slot names. Finally, the new instance's set
of slots is used for the original object with the new class, so it
preserves its identity.
Returns a singleton specification. Singletons can be used as type
specifications that have only one element in them so you can
specialize methods on unique objects.
This is actually just a list with the symbol `singleton' in its head
and the value, but this function uses a hash table to always return
the same object for the same value. For example:
=> (singleton 1)
(singleton 1)
=> (eq? (singleton 1) (singleton 1))
#t
but if the input objects are not `eq?', the result isn't either:
=> (eq? (singleton "1") (singleton "1"))
#f
Only `eq?' is used to compare objects.
Also note that MzScheme struct types are converted to appropriate
Swindle classes. This way, it is possible to have Swindle generic
functions that work with struct type specializers.
Accessors for method objects. `method-arity' is not really an
accessor, it is deduced from the arity of the procedure (minus one for
the `call-next-method' argument).
This is the "mother of all classes": every Swindle class is an
instance of `<class>'.
Slots:
* direct-supers: direct superclasses
* direct-slots: direct slots, each a list of a name and options
* cpl: class precedence list (classes list this to <top>)
* slots: all slots (like direct slots)
* nfields: number of fields
* field-initializers: a list of functions to initialize slots
* getters-n-setters: an alist of slot-names, getters, and setters
* name: class name (usually the defined identifier)
* initializers: procedure list that perform additional initializing
See the `clos' documentation for available class and slot keyword
arguments and their effect.
The class of all procedures classes, both standard Scheme procedures
classes and entity (Swindle procedure objects) classes. (Note that
this is a class of *classes*).
The class of entity classes -- generic functions and methods. An
entity is a procedural Swindle object, something that you can apply as
a function but it is still a Swindle object. Note that this is the
class of entity *classes* not of entities themselves.
Instance of `<class>', subclass of `<procedure-class>'.
The class of generic functions: objects that contain method objects
and calls the appropriate ones when applied.
Slots:
* methods: a list of <method> objects
* arity: the generic arity (same for all of its methods)
* name: generic name
* combination: a method combination function or #f, see
`make-generic-combination' below for details
Instance of `<entity-class>', subclass of `<object>', `<function>'.
The class of methods: objects that are similar to Scheme closures,
except that they have type specifiers attached. Note that in contrast
to Tiny CLOS, methods are applicable objects in Swindle -- they check
supplied argument types when applied.
Slots:
* specializers: a list of class (and singleton) specializers
* procedure: the function (never call directly!)
* qualifier: some qualifier tag, used when applying a generic
* name: method name
Instance of `<entity-class>', subclass of `<object>', `<function>'.
Convenience functions
These are some convenience functions -- no new syntax, just function
wrappers for `make' with some class and some slot values. See `clos'
for a more sophisticated (and convenient) approach.
Creates a method object -- an instance of <method>, using the given
specializer list and procedure. The procedure should have a first
argument which is being used to access a `call-next-method' call.
These two generic functions are equivalents to the ones in CL. The
first one is applied on a generic and a method in case there was no
next method and `call-next-method' was used. The second is used when
a generic was called but no matching primary methods were found. The
only difference is that in Swindle methods can be applied directly,
and if `call-next-method' is used, then `no-next-method' gets `#f' for
the generic argument.
Generics in the instance initialization protocol
The following generic functions are used as part of the protocol of
instantiating an instance, and some are used specifically to instantiate
class objects.
This generic function is called to allocate an instance of a class.
It is applied on the class object, and is expected to return the new
instance object of that class.
This generic is called to initialize an instance. It is applied on
the newly allocated object and the given initargs, and is not expected
to return any meaningful value -- only do some side effects on the
instance to initialize it. When overriding this for a some class, it
is not a good idea to skip `call-next-method' since it is responsible
for initializing slot values.
This generic is used to get a getter and setter functions for a given
slot. It is passed the class object, the slot information (a list of
a slot name and options), and an allocator function. The allocator is
a function that gets an initializer function and returns an index
position for the new slot. The return value should be a list of two
elements -- a getter and a setter functions.
This generic is used to get the class-precedence-list for a class
object. The standard <class> object uses the `compute-std-cpl' (see
in the code) which flattens the class ancestors using a topological
sort that resolve ambiguities left-to-right.
This generic is used to compute all slot information for a given
class, after its precedence list has been computed. The standard
<class> collects information from all preceding classes.
This generic is used to compute the object (a closure) that is
actually applied to execute the generic call. The standard version
uses `compute-method' and `compute-apply-methods' below, and caches
the result.
Computes the methods that should be applied for this generic
invocation with args. The standard code filters applicable methods
and sorts them according to their specificness. The return value is
expected to depend only on the types of the arguments (and values if
there are singleton specializers).
Get a generic and return a function that gets two methods and a list
of arguments and decide which of the two methods is more specific.
This decision should only be based on the argument types, or values
only in case of singletons.
Gets a generic and returns a function that gets the given arguments
for this call. This function which it returns is the combination of
all given methods. The standard one arranges them by default using
the `call-next-method' argument that methods have. Swindle extends
this with qualified methods and applies `before', `after', and
`around' methods in a similar way to CLOS: first the `around' methods
are applied (and they usually call their `call-next-method' to
continue but can return a different value), then all the `before'
methods are applied (with no `call-next-method'), then all `primary'
methods as usual (remembering the return value), and finally the
`after' methods (similar to the `before', but in reverse specificness
order). If the generic has a `combination' slot value, then it is a
procedure that is used to combine the primary methods, but the
auxiliary ones are still applied in the same way. This is unlike CLOS
where the standard combinations run only `around' methods, and there
is generally more control with method combinations, but in Swindle
`compute-apply-methods' should be overridden for this. See
`make-generic-combination' for details about method combinations.
This generic function is called to add a method to a generic function
object. This is an other change from the original Tiny CLOS where it
was a normal function.
This function can be used to construct simple method combinations that
can be used with the `combination' slot of generic functions. The
combination itself is a function that gets a generic and returns a
function that gets a list of method/procedure pairs (for optimization
the method-procedures are pre taken) and the arguments and performs
the call -- but this is only interesting if there's any need to
implement a method combination directly, otherwise, the
`make-generic-combination' interface should allow enough freedom.
Note that when a method combination is used, `around', `before', and
`after' are called around the primary call as usual, but the primaries
are never called with a valid `call-next-method' argument.
The keyword arguments that can be taken determine the behavior of this
combination. Overall, it is roughly like a customizable version of a
fold operation on the method calls.
* :init
- The initial value for this computation. Defaults to null.
* :combine
- A function to be called on a method call result and the old value,
and produces a new value. The default is `cons', which with an
initial null value will collect the results into a reversed list.
* :process-methods
- A function that can be called on the initial list of
method/procedure pairs to change it -- for example, it can be
reversed to apply the methods from the least specific to the most
specific. No default.
* :process-result
- A function that can be called on the final resulting value to
produce the actual return value. For example, it can reverse back
a list of accumulated values. No default.
* :control
- If this parameter is specified, then the `:combine' argument is
ignored. The value given to `:control' should be a function of
four arguments:
1. a `loop' function that should be called on some new value and
some new tail;
2. a `val' argument that gets the current accumulated value;
3. a `this' thunk that can be called to apply the current method
and return its result;
4. a `tail' value that holds the rest of the method/procedure list
which can be sent to `loop'.
It should be clear now, that a `:control' argument can have a lot
of control on the computation, it can abort, change arbitrary
values and skip calling methods. Note that if it won't call
`loop' with an empty list, then a `:process-result' function will
not be used as well. See the pre-defined combinations in the
source code to see examples of using this function.
These are all functions that can be used as a `combination' value for
a generic function. They work in the same way as the standard method
combinations of CL. Most of them do the obvious thing based on some
function to combine the result. The `begin' combination simply
executes all methods one by one and returns the last value, the `and'
and `or' combinations will call them one by one until a false or true
result is returned. The source of these can be used as templates for
defining more combinations.
More class functionality
(In the following, a `class' can be a class, a singleton specifier, or a
struct type.)
Setting this parameter to #t will make Swindle perform sanity checks
on given initargs for creating an object. This will make things
easier for debugging, but also slower. Defaults to `#f'. Note that
the sanity checks are done in `initialize'.
Create an instance of `class', which can be any Swindle class (except
for some special top-level classes and built-in classes).
See the `Object Initialization Protocol' below for a description of
generic functions that are involved in creating a Swindle object.
This is similar to:
(letrec ([name (make class arg ...)] ...)
(values name ...))
except that the names are first bound to allocated instances with no
initargs, and then they are initialized with all these bindings. This
is useful for situations where creating some instances needs other
instances as values. One sample usage is the way `defclass' makes the
class binding available for slot specifications like `:type'. Note
that this is a special form, which invokes `allocate-instance' and
`initialize' directly, so specializing `make' on some input will not
change the way `rec-make' works.
These are also classes for built-in objects, but they are classes for
MzScheme structs -- which can be used like Swindle classes since they
will get converted to appropriate Swindle subclasses of `<struct>'.
`<opaque-struct>' is a class of structs that are hidden -- see the
documentation for `struct-info' and the `skipped?' result. Note that
structs can be used as long as they can be inspected -- otherwise, we
can't even know that they are structs with `struct?' (this means that
<opaque-struct> can only appear in the cpl of a struct class that
inherits from a struct which is not under the current inspector).
This is the initialization protocol. All of these are generic
functions (unlike the original Tiny CLOS). See the individual
descriptions above for more details.
make
allocate-instance
initialize
class initialization only:
compute-cpl
compute-slots
compute-getter-and-setter
method initialization only:
compute-apply-method
add-method
compute-apply-generic
compute-methods
compute-method-more-specific?
compute-apply-methods