Previous Table of Contents Next


5.4.7. Operator Method Combination

The method combination described in the previous section is known as standard method combination. CLOS supports combining methods in other ways. In particular, CLOS supports operator method combination, which can be most easily understood as a standard Lisp operator applied to all the applicable primary methods:

    (defgeneric weight (x)
       (:method-combination +))

The weight method uses the + method combination and any defmethod on weight must have + as the second argument. Here’s some code that defines classes with weights:

    (defclass chassis () ())
    (defclass engine () ())
    (defclass wheels () ())
    (defclass car (chassis engine wheels) ())

    (defmethod weight + ((ch chassis)) 1500)
    (defmethod weight + ((e engine)) 500)
    (defmethod weight + ((w wheels)) 150)

Calling weight on an instance of car returns the sum of the individual weights:

   (weight (make-instance ‘car)) returns 2150

The operators supported are +, and, append, list, max, min, nconc, or, and progn.

5.4.8. Redefining Classes and Changing the Class of Instances

CLOS permits a great deal of flexibility in class redefinition. Evolving applications requires changing the design, sometimes radically. Getting the design right is usually an incremental process that can take many steps, and the ability to do this interactively greatly speeds the development process.

Many CAD applications permit classes to be redefined at runtime and the instances must be updated accordingly.

It is sometimes convenient to change the class of instances. For example, in a scheduling application, tasks may belong to classes that represent real resources, and in trying different schedules, you might not need to consider a particular task. The easiest way to handle this may be to change the class of the task to one where all the methods returning the resource constraints instead return no resource use.

5.4.8.1. Class Redefinition

Classes can be redefined at any time. The new class definition completely replaces the old one. The class redefinition may include adding or deleting slots, changing its superclasses, changing accessors, or changing any other option to defclass.

The new class redefinition affects not only all instances of the class, but all its subclasses and their instances, and CLOS automatically propagates the changes to all the affected elements. As with the rest of CLOS, the change can be customized (as discussed in the next section).

CLOS specifies that updating an instance happens before a slot of the instance is accessed, and CLOS only updates instances as needed. This lazy update model ensures that all instances appear correctly updated as soon as the class is changed while actually expending the cost of the update over only the instances that are used.

The slots of an instance are updated according to the following default rules:

  Slots occurring in both the old and the new definition keep their value. If they are unbound, then they remain unbound.
  Slots specified in the new definition but not in the old are added to the instance according the usual CLOS initialization protocol.
  Slots specified in the old definition but not in the new are discarded. The values of the old slots can be used before the update occurs by specializing the generic function update-instance-for-redefined-class.
  Shared slots in the old definition that become local slots in the new definition keep their value. If they are unbound, then they remain unbound.
  Local slots in the old definition that become shared slots in the new definition are initialized in the usual way.

As an example, I redefine square. The original definition is

   (defclass square (convex-region)
       ((number-of-sides :initform 4 :allocation :class)
        (lower-left :initform (make-point :x 0 :y 0)
                   :initarg :lower-left
                   :accessor lower-left)
        (upper-right:initform (make-point :x 1 y 1)
                   :initarg :upper-right
                   :accessor upper-right)))

and making an instance is

   (setf *s* (make-instance ‘square
               :lower-left (make-instance ‘point :x 2 :y 2)
               :upper-right (make-instance ‘point :x 3 :y 3)))

Now I redefine it as

   (defclass square (convex-region)
      ((number-of-sides :allocation :class)
      (xlow :initarg :xlow :accessor xlow :initform 0)
      (xhigh :initarg :xhigh :accessor xhigh :initform 1)
      (ylow :initarg :ylow :accessor ylow :initform 0)
      (yhigh :initarg :yhigh :accessor yhigh :initform 1)))

The only slot that is preserved is number-of-sides.

You need to update *s* properly, and you do it by putting an after method on update-instance-for-redefined-class. Note that the method definition has to be done before the new class definition; otherwise, if *s* were accessed after the class redefinition but before you defined the method, the slots would be defaulted to the new initforms:

    (defmethod update-instance-for-redefined-class :after
            ((sq square) new-slots old-slots plist &rest initargs)
       (let ((ll (getf plist ‘lower-left))
            (ur (getf plist ‘upper-right)))
         (when (and ll ur)
           (setf (slot-value sq ‘xlow) (x-coordinate ll))
           (setf (slot-value sq ‘ylow) (y-coordinate ll))
           (setf (slot-value sq ‘xhigh) (x-coordinate ur))
           (setf (slot-value sq ‘yhigh) (y-coordinate ur)))))

On accessing the instance (xlow *s*), you get the value 2.

To preserve the original interface, you use the following:

    (defmethod lower-left ((sq square))
       (make-instance ‘point :x (xlow sq) :y (ylow sq)))

    (defmethod upper-right ((sq square))
       (make-instance ‘point :x (xhigh sq) :y (yhigh sq)))


Previous Table of Contents Next