Finally, using accessor functions makes your code tidier since it helps you avoid lots of uses of the rather verbose SLOT-VALUE function.
It's trivial to define a function that reads the value of the balance slot.
(defun balance (account)
(slot-value account 'balance))
However, if you know you're going to define subclasses of bank-account, it might be a good idea to define balance as a generic function. That way, you can provide different methods on balance for those subclasses or extend its definition with auxiliary methods. So you might write this instead:
(defgeneric balance (account))
(defmethod balance ((account bank-account))
(slot-value account 'balance))
As I just discussed, you don't want callers to be able to directly set the balance, but for other slots, such as customer-name, you may also want to provide a function to set them. The cleanest way to define such a function is as a SETF function.
A SETF function is a way to extend SETF, defining a new kind of place that it knows how to set. The name of a SETF function is a two-item list whose first element is the symbol setf and whose second element is a symbol, typically the name of a function used to access the place the SETF function will set. A SETF function can take any number of arguments, but the first argument is always the value to be assigned to the place.[189] You could, for instance, define a SETF function to set the customer-name slot in a bank-account like this:
(defun (setf customer-name) (name account)
(setf (slot-value account 'customer-name) name))
After evaluating that definition, an expression like the following one:
(setf (customer-name my-account) "Sally Sue")
will be compiled as a call to the SETF function you just defined with "Sally Sue" as the first argument and the value of my-account as the second argument.
Of course, as with reader functions, you'll probably want your SETF function to be generic, so you'd actually define it like this:
(defgeneric (setf customer-name) (value account))
(defmethod (setf customer-name) (value (account bank-account))
(setf (slot-value account 'customer-name) value))
And of course you'll also want to define a reader function for customer-name.
(defgeneric customer-name (account))
(defmethod customer-name ((account bank-account))
(slot-value account 'customer-name))
This allows you to write the following:
(setf (customer-name *account*) "Sally Sue") ==> "Sally Sue"
(customer-name *account*) ==> "Sally Sue"
There's nothing hard about writing these accessor functions, but it wouldn't be in keeping with The Lisp Way to have to write them all by hand. Thus, DEFCLASS supports three slot options that allow you to automatically create reader and writer functions for a specific slot.
The :reader option specifies a name to be used as the name of a generic function that accepts an object as its single argument. When the DEFCLASS is evaluated, the generic function is created, if it doesn't already exist. Then a method specializing its single argument on the new class and returning the value of the slot is added to the generic function. The name can be anything, but it's typical to name it the same as the slot itself. Thus, instead of explicitly writing the balance generic function and method as shown previously, you could change the slot specifier for the balance slot in the definition of bank-account to this:
(balance
:initarg :balance
:initform 0
:reader balance)
The :writer option is used to create a generic function and method for setting the value of a slot. The function and method created follow the requirements for a SETF function, taking the new value as the first argument and returning it as the result, so you can define a SETF function by providing a name such as (setf customer-name). For instance, you could provide reader and writer methods for customer-name equivalent to the ones you just wrote by changing the slot specifier to this:
(customer-name
:initarg :customer-name
:initform (error "Must supply a customer name.")
:reader customer-name
:writer (setf customer-name))
Since it's quite common to want both reader and writer functions, DEFCLASS also provides an option, :accessor, that creates both a reader function and the corresponding SETF function. So instead of the slot specifier just shown, you'd typically write this:
(customer-name
:initarg :customer-name
:initform (error "Must supply a customer name.")
:accessor customer-name)
Finally, one last slot option you should know about is the :documentation option, which you can use to provide a string that documents the purpose of the slot. Putting it all together and adding a reader method for the account-number and account-type slots, the DEFCLASS form for the bank-account class would look like this:
(defclass bank-account ()
((customer-name
:initarg :customer-name
:initform (error "Must supply a customer name.")
:accessor customer-name
:documentation "Customer's name")
(balance
:initarg :balance
:initform 0
:reader balance
:documentation "Current account balance")
(account-number
:initform (incf *account-numbers*)
:reader account-number
:documentation "Account number, unique within a bank.")
(account-type
:reader account-type
:documentation "Type of account, one of :gold, :silver, or :bronze.")))
WITH-SLOTS and WITH-ACCESSORS
While 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:
189
One consequence of defining a SETF function—say, (setf foo)—is that if you also define the corresponding accessor function, foo in this case, you can use all the modify macros built upon SETF, such as INCF, DECF, PUSH, and POP, on the new kind of place.