[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2 Defining New Classes

[ *fixme* Somewhere in this manual there needs to be an introductory discussion about GOOPS classes, generic functions and methods, covering

Some of this is covered in the Tutorial chapter, in 4.5.1 Generic functions and methods - perhaps the best solution would be to expand the discussion there. ]

2.2.1 Basic Class Definition  
2.2.2 Class Options  
2.2.3 Slot Options  
2.2.4 Class Definition Internals  
2.2.5 Customizing Class Definition  
2.2.6 STKlos Compatibility  


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2.1 Basic Class Definition

New classes are defined using the define-class syntax, with arguments that specify the classes that the new class should inherit from, the direct slots of the new class, and any required class options.

syntax: define-class name (super ...) slot-definition ... . options
Define a class called name that inherits from supers, with direct slots defined by slot-definitions and class options options. The newly created class is bound to the variable name name in the current environment.

Each slot-definition is either a symbol that names the slot or a list,

 
(slot-name-symbol . slot-options)

where slot-name-symbol is a symbol and slot-options is a list with an even number of elements. The even-numbered elements of slot-options (counting from zero) are slot option keywords; the odd-numbered elements are the corresponding values for those keywords.

options is a similarly structured list containing class option keywords and corresponding values.

The standard GOOPS class and slot options are described in the following subsections: see 2.2.2 Class Options and 2.2.3 Slot Options.

Example 1. Define a class that combines two pre-existing classes by inheritance but adds no new slots.

 
(define-class <combined> (<tree> <bicycle>))

Example 2. Define a regular-polygon class with slots for side length and number of sides that have default values and can be accessed via the generic functions side-length and num-sides.

 
(define-class <regular-polygon> ()
  (sl #:init-value 1 #:accessor side-length)
  (ns #:init-value 5 #:accessor num-sides))

Example 3. Define a class whose behavior (and that of its instances) is customized via an application-defined metaclass.

 
(define-class <tcpip-fsm> ()
  (s #:init-value #f #:accessor state)
  ...
  #:metaclass <finite-state-class>)


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2.2 Class Options

class option: #:metaclass metaclass
The #:metaclass class option specifies the metaclass of the class being defined. metaclass must be a class that inherits from <class>. For an introduction to the use of metaclasses, see 2.1.1 Metaobjects and the Metaobject Protocol and 2.1.2.1 Metaclass.

If the #:metaclass option is absent, GOOPS reuses or constructs a metaclass for the new class by calling ensure-metaclass (see section ensure-metaclass).

class option: #:name name
The #:name class option specifies the new class's name. This name is used to identify the class whenever related objects - the class itself, its instances and its subclasses - are printed.

If the #:name option is absent, GOOPS uses the first argument to define-class as the class name.

class option: #:environment environment
*fixme* Not sure about this one, but I think that the #:environment option specifies the environment in which the class's getters and setters are computed and evaluated.

If the #:environment option is not specified, the class's environment defaults to the top-level environment in which the define-class form appears.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2.3 Slot Options

slot option: #:allocation allocation
The #:allocation option tells GOOPS how to allocate storage for the slot. Possible values for allocation are

The default value is #:instance.

Slot allocation options are processed when defining a new class by the generic function compute-get-n-set, which is specialized by the class's metaclass. Hence new types of slot allocation can be implemented by defining a new metaclass and a method for compute-get-n-set that is specialized for the new metaclass. For an example of how to do this, see 2.2.5 Customizing Class Definition.

slot option: #:slot-ref getter
slot option: #:slot-set! setter
The #:slot-ref and #:slot-set! options must be specified if the slot allocation is #:virtual, and are ignored otherwise.

getter should be a closure taking a single instance parameter that returns the current slot value. setter should be a closure taking two parameters - instance and new-val - that sets the slot value to new-val.

slot option: #:getter getter
slot option: #:setter setter
slot option: #:accessor accessor
These options, if present, tell GOOPS to create generic function and method definitions that can be used to get and set the slot value more conveniently than by using slot-ref and slot-set!.

getter specifies a generic function to which GOOPS will add a method for getting the slot value. setter specifies a generic function to which GOOPS will add a method for setting the slot value. accessor specifies an accessor to which GOOPS will add methods for both getting and setting the slot value.

So if a class includes a slot definition like this:

 
(c #:getter get-count #:setter set-count #:accessor count)

GOOPS defines generic function methods such that the slot value can be referenced using either the getter or the accessor -

 
(let ((current-count (get-count obj))) ...)
(let ((current-count (count obj))) ...)

- and set using either the setter or the accessor -

 
(set-count obj (+ 1 current-count))
(set! (count obj) (+ 1 current-count))

Note that

If the specified names are already bound in the top-level environment to values that cannot be upgraded to generic functions, those values are overwritten during evaluation of the define-class that contains the slot definition. For details, see ensure-generic.

slot option: #:init-value init-value
slot option: #:init-form init-form
slot option: #:init-thunk init-thunk
slot option: #:init-keyword init-keyword
These options provide various ways to specify how to initialize the slot's value at instance creation time. init-value is a fixed value. init-thunk is a procedure of no arguments that is called when a new instance is created and should return the desired initial slot value. init-form is an unevaluated expression that gets evaluated when a new instance is created and should return the desired initial slot value. init-keyword is a keyword that can be used to pass an initial slot value to make when creating a new instance.

If more than one of these options is specified for the same slot, the order of precedence, highest first is

If the slot definition contains more than one initialization option of the same precedence, the later ones are ignored. If a slot is not initialized at all, its value is unbound.

In general, slots that are shared between more than one instance are only initialized at new instance creation time if the slot value is unbound at that time. However, if the new instance creation specifies a valid init keyword and value for a shared slot, the slot is re-initialized regardless of its previous value.

Note, however, that the power of GOOPS' metaobject protocol means that everything written here may be customized or overridden for particular classes! The slot initializations described here are performed by the least specialized method of the generic function initialize, whose signature is

 
(define-method (initialize (object <object>) initargs) ...)

The initialization of instances of any given class can be customized by defining a initialize method that is specialized for that class, and the author of the specialized method may decide to call next-method - which will result in a call to the next less specialized initialize method - at any point within the specialized code, or maybe not at all. In general, therefore, the initialization mechanisms described here may be modified or overridden by more specialized code, or may not be supported at all for particular classes.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2.4 Class Definition Internals

Implementation notes: define-class expands to an expression which

syntax: class name (super ...) slot-definition ... . options
Return a newly created class that inherits from supers, with direct slots defined by slot-definitions and class options options. For the format of slot-definitions and options, see define-class.

Implementation notes: class expands to an expression which

procedure: make-class supers slots . options
Return a newly created class that inherits from supers, with direct slots defined by slots and class options options. For the format of slots and options, see define-class, except note that for make-class, slots and options are separate list parameters: slots here is a list of slot definitions.

Implementation notes: make-class

procedure: ensure-metaclass supers env
Return a metaclass suitable for a class that inherits from the list of classes in supers. The returned metaclass is the union by inheritance of the metaclasses of the classes in supers.

In the simplest case, where all the supers are straightforward classes with metaclass <class>, the returned metaclass is just <class>.

For a more complex example, suppose that supers contained one class with metaclass <operator-class> and one with metaclass <foreign-object-class>. Then the returned metaclass would be a class that inherits from both <operator-class> and <foreign-object-class>.

If supers is the empty list, ensure-metaclass returns the default GOOPS metaclass <class>.

GOOPS keeps a list of the metaclasses created by ensure-metaclass, so that each required type of metaclass only has to be created once.

The env parameter is ignored.

procedure: ensure-metaclass-with-supers meta-supers
ensure-metaclass-with-supers is an internal procedure used by ensure-metaclass (see section ensure-metaclass). It returns a metaclass that is the union by inheritance of the metaclasses in meta-supers.

The internals of make, which is ultimately used to create the new class object, are described in 2.3.2 Customizing Instance Creation, which covers the creation and initialization of instances in general.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2.5 Customizing Class Definition

During the initialization of a new class, GOOPS calls a number of generic functions with the newly allocated class instance as the first argument. Specifically, GOOPS calls the generic function

where class is the newly allocated class instance, and the default initialize method for arguments of type <class> calls the generic functions

If the metaclass of the new class is something more specialized than the default <class>, then the type of class in the calls above is more specialized than <class>, and hence it becomes possible to define generic function methods, specialized for the new class's metaclass, that can modify or override the default behaviour of initialize, compute-cpl or compute-get-n-set.

compute-cpl computes the class precedence list ("CPL") for the new class (see section 4.4.4 Class precedence list), and returns it as a list of class objects. The CPL is important because it defines a superclass ordering that is used, when a generic function is invoked upon an instance of the class, to decide which of the available generic function methods is the most specific. Hence compute-cpl could be customized in order to modify the CPL ordering algorithm for all classes with a special metaclass.

The default CPL algorithm is encapsulated by the compute-std-cpl procedure, which is in turn called by the default compute-cpl method.

procedure: compute-std-cpl class
Compute and return the class precedence list for class according to the algorithm described in 4.4.4 Class precedence list.

compute-slots computes and returns a list of all slot definitions for the new class. By default, this list includes the direct slot definitions from the define-class form, plus the slot definitions that are inherited from the new class's superclasses. The default compute-slots method uses the CPL computed by compute-cpl to calculate this union of slot definitions, with the rule that slots inherited from superclasses are shadowed by direct slots with the same name. One possible reason for customizing compute-slots would be to implement an alternative resolution strategy for slot name conflicts.

compute-get-n-set computes the low-level closures that will be used to get and set the value of a particular slot, and returns them in a list with two elements.

The closures returned depend on how storage for that slot is allocated. The standard compute-get-n-set method, specialized for classes of type <class>, handles the standard GOOPS values for the #:allocation slot option (see section allocation). By defining a new compute-get-n-set method for a more specialized metaclass, it is possible to support new types of slot allocation.

Suppose you wanted to create a large number of instances of some class with a slot that should be shared between some but not all instances of that class - say every 10 instances should share the same slot storage. The following example shows how to implement and use a new type of slot allocation to do this.

 
(define-class <batched-allocation-metaclass> (<class>))

(let ((batch-allocation-count 0)
      (batch-get-n-set #f))
  (define-method (compute-get-n-set (class <batched-allocation-metaclass>) s)
    (case (slot-definition-allocation s)
      ((#:batched)
       ;; If we've already used the same slot storage for 10 instances,
       ;; reset variables.
       (if (= batch-allocation-count 10)
           (begin
             (set! batch-allocation-count 0)
             (set! batch-get-n-set #f)))
       ;; If we don't have a current pair of get and set closures,
       ;; create one.  make-closure-variable returns a pair of closures
       ;; around a single Scheme variable - see goops.scm for details.
       (or batch-get-n-set
           (set! batch-get-n-set (make-closure-variable)))
       ;; Increment the batch allocation count.
       (set! batch-allocation-count (+ batch-allocation-count 1))
       batch-get-n-set)

      ;; Call next-method to handle standard allocation types.
      (else (next-method)))))

(define-class <class-using-batched-slot> ()
  ...
  (c #:allocation #:batched)
  ...
  #:metaclass <batched-allocation-metaclass>)

The usage of compute-getter-method and compute-setter-method is described in 3. MOP Specification.

compute-cpl and compute-get-n-set are called by the standard initialize method for classes whose metaclass is <class>. But initialize itself can also be modified, by defining an initialize method specialized to the new class's metaclass. Such a method could complete override the standard behaviour, by not calling (next-method) at all, but more typically it would perform additional class initialization steps before and/or after calling (next-method) for the standard behaviour.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2.6 STKlos Compatibility

If the STKlos compatibility module is loaded, define-class is overwritten by a STKlos-specific definition; the standard GOOPS definition of define-class remains available in standard-define-class.

syntax: standard-define-class name (super ...) slot-definition ... . options
standard-define-class is equivalent to the standard GOOPS define-class.


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Ingo Ruhnke on September, 12 2002 using texi2html