Previous | Table of Contents | Next |
The idea is simple: Set the slot value in the second object to any value (e.g., unknown) at the same time the value in the third object is changed. Changing the slot value in the second object triggers the after method to make available the update function to the first object. Thereafter, the value in the first object is subject to recalculation as desired. If you do access the value in the first object, it accesses the value in the second object for its recalculation, which is also recalculated on demand. Heres the new version of add-dependency, which does the job:
(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)) (add-update this-object this-slot update-fcn) (setf (getf (part-dependency newobj) newslot) #(lambda () (let ((fcn (getf (part-updates this-object) this-slot)) (fcn1 (getf (part-unused-updates this-object) this-slot)) (unless fcn (setf (slot-value this-object this-slot) unknown)) (setf (getf (part-updates this-object) this-slot) fcn1)))))))
The change is in the lambda expressionthat is, the function that gets run whenever (setf slot-value) is called on a slot that this-slot of this-object depends on. It checks to see if there is an update function that needs to be run when this-slot is read from. If there is, then it returns because the slot is out of date (i.e., someone else already installed the update function for the slot). Otherwise, it setfs the slot value to unknown (thus recursively notifying slots and objects that are dependent on this value) and then takes the update function out of the updates-unused association list and puts it back in the association list of the updates slot of this-object, where it is found and run next time this-slot is accessed.
This solution ignores many issues that robust code would have to deal with. More sophisticated implementations might want to set multiple dependencies on a slot of an object.
More customization might also be needed for dependency-class: For example, you would want to deal with the possibility of the slots being unbound. The following code indicates useful MOP generic functions:
(defgeneric slot-unboundp-using-class (class instance slot-name) ;; generic function for testing if a slot has a value ) (defgeneric slot-makunbound-using-class (class instance slot-name) ;; generic function to make a slot have no value )
In general, the default CLOS operations are highly optimized. Generic functions dispatch using caching techniques.
Build it right and get it working before you speed up anything. When you have to speed it up, profile your code! Profiling is really the only way to ensure you arent wasting your time fixing things that matter little.
Here are a few hints for increasing speed when it is really necessary:
For a more general book about improving performance in Common Lisp applications, you should look at Paradigms of AI Programming (Norvig, 1992), which has an excellent section on optimizing general Common Lisp programs.
Previous | Table of Contents | Next |