| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
In CLOS-like object systems, the object system is built on
top of itself—that is, things such as the structure of the class,
how a class is created, how an instance is created and initialized,
and how a method is dispatched and called, are all defined in terms
of the object system. For example, a class is just an instance of
the class <class> that defines a generic structure and behavior
of standard classes. If you subclass <class>, then you can
create your own set of classes that behaves differently than the
default behavior; in effect, you are creating your own object system.
Metaobject protocols are the definitions of APIs concerning about how the object systems are built—building-block classes, and the names and orders of generic functions to be called during operations of the object system. Subclassing these classes and specializing these methods are the means of customizing object system behaviors.
| 7.5.1 Class instantiation | ||
| 7.5.2 Customizing slot access | ||
| 7.5.3 Method instantiation | ||
| 7.5.4 Customizing method application |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Every class is an instance of a group of special classes.
A class that can be a class of another class is called metaclass.
In Gauche, only the <class> class or its subclasses
can be a metaclass.
define-classThe define-class macro is basically a wrapper of
the code that creates an instance of <class>
(or specified metaclass) and bind it to the given name.
Suppose you have the following define-class form.
(define-class name (supers) slot-specs options …) |
It is expanded into a form like this (you can see the exact form
by looking at the definition of define-class macro in
‘`gauche-config --syslibdir`/gauche/object.scm’).
(define name
(let ((tmp1 (make metaclass
:name 'name :supers (list supers)
:slots (map process-slot-definitions
slot-specs)
:defined-modules (list (current-module))
options …)))
… check class redefinition …
… registering accessor methods …
tmp1))
|
The created class’s class, i.e. metaclass, is determined by the following rules.
:metaclass option is given to the define-class
macro, its value is used. The value must be the <class>
class or its descendants.
<class>, then
the created class’s metaclass is also <class>.
<class> or another metaclass
A, then the created class’ metaclass is A.
A, B, C …) other than <class>,
then the created class’ metaclass
is a metaclass that inherits all of those metaclasses
A, B, C ….
The class’s name, superclasses, and slot definitions are passed
as the initialization arguments to the make generic function,
with other arguments passed to define-class.
The initialization argument defined-modules is passed to
remember which module the class is defined, for the redefinition of
this class.
The slot specifications slot-specs are processed
by internal method process-slot-definitions
(which can’t be directly called) to be turned
into slot definitions. Specifically, an :init-form slot
option is turned into an :init-thunk option, and
:getter, :setter and :accessor slot
options are quoted.
After the class (an instance of metaclass) is created, the global binding of name is checked. If it is bound to a class, then the class redefinition protocol is invoked (see Class redefinition).
Then, the methods given to :getter, :setter
and :accessor slot options in slot-spec are
collected and registered to the corresponding generic functions.
The base class of all metaclasses, <class>, has
the following slots. Note that these slots are for internal management,
and users shouldn’t change their values after
the class is initialized. It is recommended to obtain information
about a class by procedures described in Class object, instead
of directly accessing those slots.
The name of the class; the symbol given to define-class macro.
class-name returns this value.
Class precedence list. class-precedence-list returns this value.
The list of direct superclasses.
class-direct-supers returns this value.
An assoc list of slot accessors—it encapsulates how each slot should be accessed.
A list of slot definitions. class-slots returns this value.
See Slot definition object, for the details of slot definitions.
A list of slot definitions that is directly specified in this
class definition (i.e. not inherited).
class-direct-slots returns this value.
The number of instance allocated slots.
A list of classes that directly inherits this class.
class-direct-subclasses returns this value.
A list of methods that has this class in its specializer list.
class-direct-methods returns this value.
The initialization argument list when this class is created. The information is used to initialize redefined class (see Class redefinition).
A list of modules where this class has a global binding.
If this class has been redefined, this slot contains a reference
to the new class. Otherwise, this slot has #f.
The value of this slot indicates how this class is created.
Scheme defined class has a symbol scheme. Other values
are for internal use.
<class>| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
These two generic functions are responsible to determine what slots a class has, and how each slot is accessed.
In the initialize method of a class,
compute-slots is called after the class’s
direct-supers, cpl and direct-slots are set.
It must decide what slots the class should have, and what
slot options each slot should have, based on those three piece
of information. The returned value should have the following
form, and it is used as the value of the slots slot of the class.
<slots> : (<slot-definition> ...) <slot-definition> : (<slot-name> . <slot-options>) <slot-name> : symbol <slot-options> : keyword-value alternating list. |
After the slots slot of the class is set by the returned
value from compute-slots,
compute-get-n-set is called for each slot
to calculate how to access and modify the slot.
The class and the slot definition are the arguments.
It must return either one of the followings:
This slot becomes n-th instance slot. This is the only way to allocate a slot per instance.
The base method of compute-get-n-set keeps track of the current
number of allocated instance slots in the class’s num-instance-slots
slot. It is not recommended for other specialized methods to use or
change the value of this slot, unless you know a very good reason to
override the object system behavior in deep down. Usually it is suffice
to call next-method to let the base method reserve an instnace
slot for you.
See the examples below for modifying instance slot access behaviors.
(get-proc set-proc bound?-proc initializable)The get-proc, set-proc and bound?-proc elements are procedures
invoked when this slot of an instance is accessed (either via
slot-ref/slot-set!/slot-bound?,
or an accessor method specified by :getter/:setter slot options).
The value other than get-proc may be #f, and can be omitted
if all the values after it is also #f. That is, the simplest
form of this type of return value is a list of one element,
get-proc.
The procedure may return #<undef> to indicate the slot is
unbound. It triggers the slot-unbound generic function.
(That is, this type of slot cannot have #<undef> as its value.)
If this element is #f or omitted, the slot becomes read-only;
any attempt to write to the slot will raise an error.
slot-bound? is called to check whether the slot of
an instance is bound, bound?-proc is called with
an argument, the instance. It should return a boolean value
which will be the result of slot-bound?.
If this element is #f or omitted, slot-bound? will
call get-proc and returns true if it returns
#<undef>.
:init-value
or :init-form.
<slot-accessor> objectAccess to this slot is redirected through the returned
slot-accessor object. See below for more on <slot-accessor>.
The value returned by compute-get-n-set is immediately passed
to compute-slot-accessor to create a slot accessor object,
which encapsulates how to access and modify the slot.
After all slot definitions are processed by compute-get-n-set
and compute-slot-accessor, an assoc list of
slot names and <slot-accessor> objects are stored in the
class’s accessors slot.
Access-specifier is a value returned from
compute-get-n-set. The base method creates an instance
of <slot-accessor> that encapsulates how to
access the given slot.
Created slot accessor objects are stored (as an assoc list using
slot names as keys) in the class’s accessors slot.
Standard slot accessors and mutators, such as slot-ref,
slot-set!, slot-bound?, and the slot accessor
methods specified in :getter, :setter and :accessor
slot options, all go through slot accessor object eventually.
Specifically, those functions and methods first looks up
the slot accessor object of the desired slot, then calls
slot-ref-using-accessor etc.
The standard method walks CPL of class and gathers all direct slots. If slots with the same name are found, the one of a class closer to class in CPL takes precedence.
The standard processes the slot definition with the following
slot allocations: :instance, :class,
each-subclass and :virtual.
The low-level slot accessing mechanism. Every function or method that needs to read or write to a slot eventually comes down to one of these functions.
Ordinary programs need not call these functions directly. If you ever need to call them, you have to be careful not to grab the reference to slot-accessor too long; if obj’s class is changed or redefined, slot-accessor can no longer be used.
Here we show a couple of small examples to illustrate how slot access
protocol can be customized. You can also look at gauche.mop.*
modules (in the source tree, look under lib/gauche/mop/)
for more examples.
The first example implements the same functionality of
:virtual slot allocation. We add :procedural
slot allocation, which adds :ref, :set! and :bound?
slot options.
(define-class <procedural-slot-meta> (<class>) ())
(define-method compute-get-n-set ((class <procedural-slot-meta>) slot)
(if (eqv? (slot-definition-allocation slot) :procedural)
(let ([get-proc (slot-definition-option slot :ref)]
[set-proc (slot-definition-option slot :set!)]
[bound-proc (slot-definition-option slot :bound?)])
(list get-proc set-proc bound-proc))
(next-method)))
|
A specialized compute-get-n-set is defined on a metaclass
<procedural-slot-meta>. It checks the slot allocation,
handles it if it is :procedural, and delegates other
slot allocation cases to next-method. This is a typical
way to add new slot allocation by layering.
To use this :procedural slot, give <procedural-slot-meta>
to a :metaclass argument of define-class:
(define-class <temp> ()
((temp-c :init-keyword :temp-c :init-value 0)
(temp-f :allocation :procedural
:ref (lambda (o) (+ (*. (ref o 'temp-c) 9/5) 32))
:set! (lambda (o v)
(set! (ref o 'temp-c) (*. (- v 32) 5/9)))
:bound? (lambda (o) (slot-bound? o 'temp-c))))
:metaclass <procedural-slot-meta>)
|
An instance of <temp> keeps a temperature in both
Celsius and Fahrenheit. Here’s an example interaction.
gosh> (define T (make <temp>)) T gosh> (d T) #<<temp> 0xb6b5c0> is an instance of class <temp> slots: temp-c : 0 temp-f : 32.0 gosh> (set! (ref T 'temp-c) 100) #<undef> gosh> (d T) #<<temp> 0xb6b5c0> is an instance of class <temp> slots: temp-c : 100 temp-f : 212.0 gosh> (set! (ref T 'temp-f) 450) #<undef> gosh> (d T) #<<temp> 0xb6b5c0> is an instance of class <temp> slots: temp-c : 232.22222222222223 temp-f : 450.0 |
Our next example is a simpler version of gauche.mop.validator.
We add a slot option :filter, which takes a procedure
that is applied to a value to be set to the slot.
(define-class <filter-meta> (<class>) ())
(define-method compute-get-n-set ((class <filter-meta>) slot)
(cond [(slot-definition-option slot :filter #f)
=> (lambda (f)
(let1 acc (compute-slot-accessor class slot (next-method))
(list (lambda (o) (slot-ref-using-accessor o acc))
(lambda (o v) (slot-set-using-accessor! o acc (f v)))
(lambda (o) (slot-bound-using-accessor? o acc))
#t)))]
[else (next-method)]))
|
The trick here is to call next-method and
compute-slot-accessor to calculate the slot accessor
and wrap it. See how this metaclass works:
(define-class <foo> () ((v :init-value 0 :filter x->number)) :metaclass <filter-meta>) gosh> (define foo (make <foo>)) foo gosh> (ref foo'v) 0 gosh> (set! (ref foo'v) "123") #<undef> gosh> (ref foo'v) 123 |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] |
This document was generated by Shiro Kawai on May 28, 2012 using texi2html 1.82.