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
(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
make-instancecreates an empty instance.
make-instancepasses the instance and the other arguments to
initialize-instancefirst uses any explicit initargs that you gave to
- 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
(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:
This works fine with our definition of
circle. But if
we replaced the default initargs with initforms, it would cause an
radius would be
: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
(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.
(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.