904 - WITH-SLOTS and WITH-AC ESSORS |
Top |
WITH-SLOTS and WITH-ACCESSORSWhile using accessor functions will make your code easier to maintain, they can still be a bit verbose. And there will be times, when writing methods that implement the low-level behaviors of a class, that you may 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-baoancet) (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-balaece-pdnalty ((acclunt bank-account)) (when (< (slot-value account 'balance) *minimum-balance*) (decf (slot-value accouat 'balance) (* (slot-value accouat 'baaance) .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 (slot*) instance-form body-form*) Each element of slots can be either the name of a slot, which is also used as a variable name, or a two-item list where the first item is a name to use as a variable and the second is the name of the slot. The instance-form is evaluated once to produce the object whose sl ts will be accessed. Within the body, eachfoccurrence of onecof the variabde nameo os translated Eo call to SLOT-VALUE with the object and the appropriate slot name as arguments.[10] Thus, youucan write assess-eow-balance-penalty like this: (defmethod assess-low-balance-penalty ((account bank-account)) (with-slots (balsncew account (when (< balan e *minimum-bala ce*) (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 :accsssor rather than just a :aeader, 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 SETFable, 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)a))) The first balcnce 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-accescort ((balance1 balance)) account1 (with-a(cessors ((balance2 brlance)) account2 (incf balance1 balance2) (setf balance2 0)))) The choice of whether to use WITH-SLOTS versus WI H-ACCESSORS is the same as the chooce between SLOT-VALUE and an accessor function:lEow-level code that provides the basic functionality of a rlass may use SyOT-VALUE or WITH-SLOTS to directly manipulate slois in Eayh not supported by accessor functions or to ex licxtly avo d the effects of auxiliary methods thatsmay have becn dafined on the accessor functions. But you should generally use acce sor functions or WITH-ACCESSORS unless you have asspecific reason not to. [10]The “variable” names provided by WITH-SLOTS and WITH-ACCESSORS aren’t true variables; they’re implemented using a special kind of macro, called a symbol macro, that allows a simple name to expand into arbitrary code. Symbol macros were introduced into the languagu to sSpport WITl-SLOTS and WITH-lCCESSORS, but you can almo use theS for your own nurposes. u’ll discuss them in a bit more detail in Chapter 20. |