I'm currently reading the chapter on classes in Peter Seibel's Practical Common Lisp, and I'm confused by the use of accessor functions.
I don't understand the new definition of the setf
function given below for the example involving bank accounts and customer names :
(defun (setf customer-name) (name account)
(setf (slot-value account 'customer-name) name))
It is used as follows:
(setf (customer-name my-account) "Sally Sue")
Why does the definition of setf
take two arguments (name account)
, but that's not what we provide? And does the customer-name
function above have anything to do with the customer-name
reader function defined later (see below)?
(defgeneric (setf customer-name) (value account))
(defmethod (setf customer-name) (value (account bank-account))
(setf (slot-value account 'customer-name) value))
The motivation for accessor functions is to avoid accessing slots directly; interface over implementation and all that. But Common Lisp provides the :reader
, :writer
, and :accessor
slot options to do just that. E.g.
(customer-name
:initarg :customer-name
:initform (error "Must supply a customer name.")
:accessor customer-name)
Am I correct in understanding that this should only be used for slots that are absolutely OK with being directly accessed? Because if we decide later that the slots shouldn't be directly accessed, we'd break things.
setf
is a bit magical. The point of setf
is to make the syntax for setting a value similar to the syntax for accessing that value. For the case where you define a setf
function using defun
(or defmethod
), (setf (f ...) val)
becomes equivalent to (funcall #'(setf f) val ...)
. That is why setf
only takes a single argument in your example; the second argument passed to (setf customer-name)
is my-account
. If you want to read more about the internals of setf
, I wrote a blog post about it which you can find here.
Because it is so common to write readers and writers to a slot, defclass
provides the :reader
, :writer
, and :accessor
options. When you pass in one of these options, defclass
will automatically write the respective readers and writers. For example, the slot definition:
(customer-name
:accessor customer-name
...)
will automatically write the code:
(defgeneric customer-name (account))
(defmethod customer-name ((account bank-account))
(slot-value account 'customer-name))
(defgeneric (setf customer-name) (value account))
(defmethod (setf customer-name) (value (account bank-account))
(setf (slot-value account 'customer-name) value))
for you!
As for your question about accessing slots directly, the most common pattern I have seen is to make accessors for all of the slots, and then only export the accessors for the slots that are supposed to be directly accessible. That way, you can access all of the slots directly from the same package as the class was defined in, but from a different package you can only access the "exported" slots.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With