Previous Table of Contents Next


Now when you set the length slot of w1, the :after method runs the function update-length.

The effect of the :after method is purely a side effect resulted from running the function named update-length that is associated with the symbol length in w1’s dependency slot. For example, it doesn’t affect setting the slot value w1 in any way. In this case, because the length slot of b1 depends on the value in the length slot of w1, you want this side effect to change the length slot of b1.

The length slot of b1 is also dependent on the width slot of b1, so now you add that dependency as well:

   (setf (getf (part-dependency b1) ‘width) #’update-length)

When you try the following:

   (setf (lngth w1) 6)
   (setf (width b1) 1)

reading the value by calling (lngth b1) returns the value 13.

A diagram will help:

At this point, you can automate the preceding process by writing a function:

   (defun add-dependency (this-object this-slot update-fcn
                        &rest objects-and-slots)
       (do* ((xx objects-and-slots (cddr xx))
          (newobj (car xx) (car xx))
        (newslot (cadr xx) (cadr xx)))
          ((null xx))
        (setf (getf (part-dependency newobj) newslot)
          #’(lambda () (setf (slot-value this-object this-slot)
                 (funcall update-fcn))))))

This is all standard Common Lisp. It iterates over the pairs of objects and slot names and adds an anonymous function (defined as the lambda expression), which sets the slot named this-slot of the object named this-object with the value returned by update-fcn whenever one of the named slots of objects in the objects-and-slots list is set. This is cleaner than having to hard wire the to-be-updated object into the definition of update-fcn, as you did in the preceding example of update-length.

Note the similarity between this lambda expression and the original update-length function:

   #’(lambda ()
      (setf (slot-value this-object this-slot)
          (funcall update-fcn))
      (defun update-length ()
         (setf (slot-value b1 ‘length); <- set the dependent slot
            (+ (width b1) (* 2 (lngth w1)) ;<- the update function
      )))

Here you add the dependency of the length slot of b1 to the length slot of w1 and the width slot of b1:

   (add-dependency b1 ‘length
        #’(lambda ()(+ (width b1) (* 2 (lngth w1)))
        w1 ‘length b1 ‘width)

add-dependency adds the update function update-length for the length slot of b1 whenever the length slot of w1 is set or the width slot of b1 is set.

Now you have spreadsheet semantics for slots. The semantics result in an immediate update of all slots, which are calculated using other slots. This type of semantics is exactly what is desired if you want to immediately see the correct values of other slots, for example, where the slots represent dimensions of parts in an engineering diagram.

Dependency Tracking Applied to Lazy Evaluation

Consider a somewhat different problem: As part of the design, you need to calculate the plane’s vibrational characteristics to model its take-off characteristics. Suppose that the calculation involves numerical algorithms that take a long time to compute and also depend on the dimensions of parts of the plane. Although you are modeling the plane, you don’t want the vibrational characteristics to be recomputed every time you change the dimensions. In a way, you want the exact opposite semantics for slot-value update that you just went through: You don’t want slots to recalculate their values until you need to use them. This issue is not an academic one: Real-life planes, such as the Boeing 777, are designed with Lisp-based software using techniques like those discussed here (Phillips, 1997). The number of parts and dependencies in very large models is enormous, so controlling the amount of recalculation is a critical performance consideration.

The system is the inverse of the one just designed, which recalculates values whenever something the slot is dependent upon changes. Instead, here, you recalculate a value only when the slot is accessed. A simple approach is to add a slot that keeps the update function and specialize slot-value-using-class to call the update function whenever the slot is read. As an example, use the weight of the plane as the sum of the weights of the wings, the engines, and the body. If the model has one engine per wing, then the weight of the plane is calculated using twice the weight of the engines, plus twice the weight of the wings, plus the weight of the body:

   (defclass part ()
      ((updates :initform nil
             :accessor part-updates)
       (unused-updates :initform nil
             :accessor part-unused-updates)
       (dependency :initform nil
             :accessor part-dependency))
       (:metaclass dependency-class))

   (defclass body (part)
      ((weight :initarg :weight :initform 0 :accessor weight))
      (:metaclass dependency-class))

   (defclass wing (part)
      ((weight :initarg :weight :initform 0 :accessor weight))
      (:metaclass dependency-class))

   (defclass engine (part)
      ((weight :initarg :weight :initform 0 :accessor weight))
      (:metaclass dependency-class))
   (defclass plane (part)
      ((weight :initform 0 :reader weight))
      (:metaclass dependency-class))

You modify slot-value to recalculate values when the slot is accessed. When slot-value is called, as part of the metaobject protocol it calls slot-value-using-class. Like (setf slot-value-using-class), and ignoring for now compiler optimizations as well as error-checking, slot-value is implemented as

   (defun slot-value (object slot-name)
      (let* ((class (class-of object))
           (slot (slot-from-name slot-name class)))
       (slot-value-using-class class object slot)))

slot-value-using-class is called by slot-value with the following arguments: the class of the object as its first argument, the object itself as the second argument, and the metaobject representing the slot as its third argument. Recall that slot-from-name was defined in the previous section.


Previous Table of Contents Next