Next in our list of functions that the newcomer to Lisp might not have encountered is initialize-instance. Now, you might immediately think of this as a function to be used by the implementation, of little interest to the programmer, but this is actually a very useful method when you’re programming with objects.
So, initialize-instance is used by the implementation as it constructs instances of objects, and is called by make-instance. What makes it interesting to the Lisp programmer? It’s that you can define a specialized :after method for make-instance. That is, after the make-instance call has done those things that you asked of it, such as filling in :initarg values and executing :initform forms, your :after method will be called to do additional work. Where the primary initialize-instance method looks like a C++ initialization list, the :after method looks like the body of the constructor. It is here that the programmer can perform initialization operations based on the values of slots in the class.
Here is an example, part of some code that I wrote for working with probability distributions:
(defclass uniform-distribution (distribution) ((low-bound :reader get-low :initarg :low :initform 0.0) (high-bound :reader get-high :initarg :high :initform 1.0) (width :reader get-width) (height :reader get-height))) (defmethod initialize-instance :after ((ud uniform-distribution) &key) (unless (< (get-low ud) (get-high ud)) (error "Bad values of low/high for uniform distribution.")) (setf (slot-value ud 'width) (float (- (get-high ud) (get-low ud)))) (setf (slot-value ud 'height) (/ 1.0 (- (get-high ud) (get-low ud)))) (set-limits ud (get-low ud) (get-high ud) (get-low ud) (get-high ud)))
This code allows the user to build a uniform-distribution object with make-instance, optionally supplying overrides for the lower and upper bounds of the distribution, the domain. Once the low-bound and high-bound slots are filled, the :after specialized initialize-instance method is called which sanity-checks the passed values and then uses them to fill in the other two slots. Finally, it calls a method on the base class (not shown) to fill in some data that the base class needs.