Previous | Table of Contents | Next |
Note that we use lngth instead of length to prevent a name clash with the system-supplied length function.
The :metaclass option specifies each class as a dependency-class. The terminology metaclass is historical syntax in the MOP for a class metaobject class. The following diagram helps illustrate the relationship between these classes:
The dashed lines show instance relations and the solid lines show class relations. On the left of the diagram are the class metaobject classes, and on the right are their instancesthat is, the class metaobjects themselves.
So far, not much has happened. dependency-class has exactly the same semantics as standard-class, so classes defined using the metaclass dependency-class have exactly the same behavior as regular CLOS classes. All subclasses of type part have a slot called dependency (which we will use a little later). You can make instances of the three subclasses as follows:
(setq w1 (make-instance wing)) (setq b1 (make-instance body))
What you want to do is model the value of the length slot of the plane as a derived quantity from the length slots of the wing and the width slot of the body. The formula might be the length of the plane is equal to the width of the body plus twice the length of the wing. In other words, you would like, for example
(setf (lngth w1) 3.5) (setf (width b1) 0.3)
to automatically result in updating the plane length:
(lngth b1) => 7.3 (7.3 = 3.5 x 2 + 0.3)
The formula should be
(defun update-length () (setf (slot-value b1 length) (+ (width b1) (* 2 (lngth w1)))))
You want to make this possible for any type of dependency. You do this by adding custom behavior to the (setf slot-value) function in the CLOS MOP. Recall that this is a function specifier (a list representing a function that is used with setf syntax; it is the function called on an object with slot-name with a new value in the form (setf (slot-value object slot-name) new-value)).
(setf slot-value-using-class) is the method that implements the behavior of the (setf slot-value) function (called when you set the length slot of w1 or the width slot of b1). (setf slot-value) is the standard CLOS methodology for setting slot values.
When (setf slot-value) is called, the MOP specifies that it use the method (setf slot-value-using-class) to decide what it should do, so you specialize this method on dependency-class to implement dependency tracking. Here is the interface definition for generic function:
(defgeneric (setf slot-value-using-class) (new-value class instance slot) ;; generic function to set a slot-value )
(setf slot-value-using-class) is called by (setf slot-value) with the following arguments: the new value as its first argument, the class of the object as its second argument, the object itself as the third argument, and the metaobject representing the slot as its fourth argument (its the argument slot above).
First, you need a function to find a slot from its name:
(defun slot-from-name (name class) (dolist (slot (class-slots class)) (when (eq name (slot-definition-name slot)) (return slot))))
Ignoring for now compiler optimizations and error-checking, (setf slot-value) is implemented in the following way:
(defun (setf slot-value) (new-value object slot-name) (let* ((class (class-of object)) (slot (slot-from-name slot-name class))) (setf(slot-value-using-class class object slot) new-value)))
Here is the specializer on dependency-class:
(defmethod (setf slot-value-using-class) :after (new-value (class dependency-class) object slot) ;; Above is the argument list. Usage of this function spec: ;; (setf (slot-value-using-class class object slot) new-value) (let*((name (slot-definition-name slot)) ;; Look for a function fcn on the dependency slot of ;; object associated with the name of the slot argument. (fcn (if (slot-boundp object dependency) (getf (part-dependency object) name)))) ;; Call the function fcn when it is present (when fcn (funcall fcn))))
You add an :after method that runs after the primary (setf slot-value-using-class) method runs. The primary method is inherited from standard-class and hence has the same semanticsthat is, it sets the slot value. This :after method is specialized on the metaclass dependency-class, so it runs every time you try to set a slot of any instance of any class of the metaclass dependency-class. Recall that the value returned by the after method is ignored, so it does not affect the value returned by the primary method when the whole method runs.
Note that classes of ordinary type standard-class are unaffected.
What does this :after method do? It looks in the dependency slot of the object. It expects to see an association lista list of pairs, each of the form (value, slot-name). The function getf looks up the value associated with slot-name of the metaobject representing the slot. If this value is present, it is run as a function that takes the object as its argument.
Note that (setf slot-value) may be called as part of the initialization of a new instance, so to prevent looking in the dependency slot before it gets initialized, you check it first to see if the initialization has happened using the standard CLOS function slot-boundp.
Its time to see it in action. Add a dependency to the model:
(setf (getf (part-dependency w1) length) #update-length)
Previous | Table of Contents | Next |