我正在阅读Peter Seibel的Practical Common Lisp中有关类的章节,我对使用访问器函数感到困惑.
我不理解setf
下面给出的涉及银行账户和客户名称的功能的新定义:
(defun (setf customer-name) (name account) (setf (slot-value account 'customer-name) name))
它使用如下:
(setf (customer-name my-account) "Sally Sue")
为什么定义setf
采用两个参数(name account)
,但那不是我们提供的?customer-name
上面的函数是否与customer-name
稍后定义的读者函数有关(见下文)?
(defgeneric (setf customer-name) (value account)) (defmethod (setf customer-name) (value (account bank-account)) (setf (slot-value account 'customer-name) value))
访问器功能的动机是避免直接访问插槽; 接口实现和所有这些.但Common Lisp提供了:reader
,:writer
和:accessor
插槽选项来做到这一点.例如
(customer-name :initarg :customer-name :initform (error "Must supply a customer name.") :accessor customer-name)
我是否正确理解这应该只用于直接访问绝对正常的插槽?因为如果我们稍后决定不应该直接访问插槽,我们就会破坏它们.
setf
有点神奇.关键setf
是要设置一个类似于访问该值的语法的语法.对于setf
使用defun
(或defmethod
)定义函数的情况,(setf (f ...) val)
变为等效(funcall #'(setf f) val ...)
.这就是为什么setf
在你的例子中只需要一个参数; 传递给的第二个参数(setf customer-name)
是my-account
.如果你想了解更多关于内部的内容setf
,我写了一篇关于它的博客文章,你可以在这里找到.
因为它是如此普遍写读者和作家的插槽,defclass
提供了:reader
,:writer
和:accessor
选项.当您传入其中一个选项时,defclass
将自动编写相应的读者和作者.例如,插槽定义:
(customer-name :accessor customer-name ...)
会自动编写代码:
(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))
为了你!
至于你直接访问插槽的问题,我看到的最常见的模式是为所有插槽创建访问器,然后只导出应该可以直接访问的插槽的访问器.这样,您可以直接从定义类的同一个包中访问所有插槽,但是从另一个包中,您只能访问"导出"插槽.