specifically want to access slots directly to set a slot that has no writer function or to get at the slot value without causing any auxiliary methods defined on the reader function to run.
This is what SLOT-VALUE
is for; however, it's still quite verbose. To make matters worse, a function or method that accesses the same slot several times can become clogged with calls to accessor functions and SLOT-VALUE
. For example, even a fairly simple method such as the following, which assesses a penalty on a bank-account
if its balance falls below a certain minimum, is cluttered with calls to balance
and SLOT- VALUE
:
(defmethod assess-low-balance-penalty ((account bank-account))
(when (< (balance account) *minimum-balance*)
(decf (slot-value account 'balance) (* (balance account) .01))))
And if you decide you want to directly access the slot value in order to avoid running auxiliary methods, it gets even more cluttered.
(defmethod assess-low-balance-penalty ((account bank-account))
(when (< (slot-value account 'balance) *minimum-balance*)
(decf (slot-value account 'balance) (* (slot-value account 'balance) .01))))
Two standard macros, WITH-SLOTS
and WITH- ACCESSORS
, can help tidy up this clutter. Both macros create a block of code in which simple variable names can be used to refer to slots on a particular object. WITH- SLOTS
provides direct access to the slots, as if by SLOT- VALUE
, while WITH-ACCESSORS
provides a shorthand for accessor methods.
The basic form of WITH-SLOTS
is as follows:
(with-slots (
Each element of SLOT-VALUE
with the object and the appropriate slot name as arguments.[190] Thus, you can write assess-low-balance-penalty
like this:
(defmethod assess-low-balance-penalty ((account bank-account))
(with-slots (balance) account
(when (< balance *minimum-balance*)
(decf balance (* balance .01)))))
or, using the two-item list form, like this:
(defmethod assess-low-balance-penalty ((account bank-account))
(with-slots ((bal balance)) account
(when (< bal *minimum-balance*)
(decf bal (* bal .01)))))
If you had defined balance
with an :accessor
rather than just a :reader
, then you could also use WITH-ACCESSORS
. The form of WITH-ACCESSORS
is the same as WITH- SLOTS
except each element of the slot list is a two-item list containing a variable name and the name of an accessor function. Within the body of WITH-ACCESSORS
, a reference to one of the variables is equivalent to a call to the corresponding accessor function. If the accessor function is SETF
able, then so is the variable.
(defmethod assess-low-balance-penalty ((account bank-account))
(with-accessors ((balance balance)) account
(when (< balance *minimum-balance*)
(decf balance (* balance .01)))))
The first balance
is the name of the variable, and the second is the name of the accessor function; they don't have to be the same. You could, for instance, write a method to merge two accounts using two calls to WITH-ACCESSORS
, one for each account.
(defmethod merge-accounts ((account1 bank-account) (account2 bank-account))
(with-accessors ((balance1 balance)) account1
(with-accessors ((balance2 balance)) account2
(incf balance1 balance2)
(setf balance2 0))))
The choice of whether to use WITH-SLOTS
versus WITH-ACCESSORS
is the same as the choice between SLOT-VALUE
and an accessor function: low-level code that provides the basic functionality of a class may use SLOT-VALUE
or WITH- SLOTS
to directly manipulate slots in ways not supported by accessor functions or to explicitly avoid the effects of auxiliary methods that may have been defined on the accessor functions. But you should generally use accessor functions or WITH-ACCESSORS
unless you have a specific reason not to.
The last slot option you need to know about is :allocation
. The value of :allocation
can be either :instance
or :class
and defaults to :instance
if not specified. When a slot has :class
allocation, the slot has only a single value, which is stored in the class and shared by all instances.
However, :class
slots are accessed the same as :instance
slots—they're accessed with SLOT-VALUE
or an accessor function, which means you can access the slot value only through an instance of the class even though it isn't actually stored in the instance. The :initform
and :initarg
options have essentially the same effect except the initform is evaluated once when the class is defined rather than each time an instance is created. On the other hand, passing an initarg to MAKE-INSTANCE
will set the value, affecting all instances of the class.
Because you can't get at a class-allocated slot without an instance of the class, class-allocated slots aren't really equivalent to
As I discussed in the previous chapter, classes inherit behavior from their superclasses thanks to the generic function machinery—a method specialized on class A
is applicable not only to direct instances of A
but also to instances of A
's subclasses. Classes also inherit slots from their superclasses, but the mechanism is slightly different.