Page 180
:initform
There are actually two ways to initialize slots in CLOS, initforms and default initargs. The latter are more commonly used.
Here's how default initargs are used when defining
circle
:
(defclass circle () ((radius :accessor circle-radius :initarg :radius) (center :accessor circle-center :initarg :center)) (:default-initargs :radius 1 :center (cons 0 0)))
What's the difference between default initarg's and initforms? A default initarg is a default value for an initarg. An initform is a default value for a slot.
So what difference does that make? Here's how it works. When you say
(make-instance 'circle ...)
make-instance
(in conjunction with
initialize-instance
) creates an instance of a circle as
follows:
make-instance
creates an empty instance.make-instance
passes the instance and the other arguments toinitialize-instance
.initialize-instance
first uses any explicit initargs that you gave tomake-instance
.- For slots still unitialized, it then uses any default initargs that have been defined.
- Finally, for slots still unitialized, it uses any default initforms that have been defined.
So one difference is that default initargs take priority over initforms.
Here's another difference. Suppose we add an area slot to circle:
(defclass circle () ((radius :accessor circle-radius :initarg :radius) (center :accessor circle-center :initarg :center) (area :accessor circle-area)) (:default-initargs :radius 1 :center (cons 0 0)))
Note that there is neither an initform nor an initarg for
area
. Instead, we are going to calculate the area from
the radius when the instance is created. Suppose we do this by
defining an after method on initialize-instance
:
(defmethod initialize-instance :after ((c circle) &key radius &allow-other-keys) (setf (circle-area c) (* pi radius radius)))
Now suppose we make the following call:
(make-instance 'circle)
This works fine with our definition of circle
. But if
we replaced the default initargs with initforms, it would cause an
error, because radius
would be nil
. No
:radius
argument was given and there was no default
value for that argument.
Note that we could define the after method on
initialize-instance
to work with either class definition
as follows:
(defmethod initialize-instance :after ((c circle) &rest args) (setf (circle-area c) (* pi (circle-radius c) (circle-radius c))))
This definition has two disadvantages:
- It's slightly more costly to access a slot than a keyword value.
- It doesn't generalize to before methods, where the slots haven't been created yet.
For this reason, many programmers use default initargs in their class definitions, rather than initforms.
Page 190
(defclass suit (jacket trousers) ())
Wrong, wrong, wrong! A suit is not a subtype of jacket or trousers. A suit consists of a jacket and trousers.
Don't use "is a" when you mean "has a." This is a classic mistake made by people in all object oriented programming environments.
One reasonable way to do this example is:
(defclass suit () ((jacket :accessor suit-jacket :initarg :jacket) (trousers :accessor suit-trousers :initarg :trousers))) (defmethod price ((jk jacket)) 350) (defmethod price ((tr trousers)) 200) (defmethod price ((s suit)) (+ (price (suit-jacket s)) (price (suit-trousers s))))
So, what is a good example requiring non-standard method combinations? Good question. I've not needed them so far.