使用点符号访问 CLOS 插槽
Using dot notation to access CLOS slots
访问 class 个槽时,而不是写入
(defmethod get-name ((somebody person) (slot-value somebody 'name))
是否可以使用点符号 aka C++,即
(defmethod get-name ((somebody person) somebody.name) ?
否则,当一个方法中有很多插槽操作时,(slot-value...
会创建很多样板代码。
我今天已经找到了答案,我只是将其作为问答发布,但是如果有更好的解决方案或者我的解决方案有问题,请随时添加新的答案或评论。
最简单的解决方案似乎是 reader 重载 .
的宏,这样 (slot-value somebody 'name)
可以写成 .somebody.name
我的策略是阅读 somebody.name
作为一个字符串(我们需要定义一个非终止宏字符,这样 reader 就不会在字符串中间停止),然后处理该字符串以构造适当的 (slot-value...
我需要两个辅助函数:
(defun get-symbol (str)
"Make an uppercase symbol"
(intern (string-upcase str)))
(defun split-string (str sep &optional (start 0))
"Split a string into lists given a character separator"
(let ((end (position sep str :start start)))
(cons (subseq str start end) (if end (split-string str sep (1+ end))))))
然后我可以定义我的 reader 宏:
(defun dot-reader (stream char)
(declare (ignore char))
(labels ((make-query (list)
(let ((car (car list))
(cdr (cdr list)))
(if cdr `(slot-value ,(make-query cdr) (quote ,(get-symbol car)))
(get-symbol car)))))
(make-query (nreverse (split-string (symbol-name (read stream)) #\.)))))
最后,我需要注册这个reader宏:
(set-macro-character #\. #'dot-reader t)
现在可以写:
(defmethod get-name ((somebody person) .somebody.name)
或者,如果 name
本身就是一个 class,
(defmethod get-name ((somebody person) .somebody.name.first-name)
一个限制是 s 表达式在点之间不起作用,比如说
.(get-my-class).name
不行。
库 access 提供了一个点符号 reader 宏来访问槽(以及哈希表和其他东西)。通过调用 (access:enable-dot-syntax) 启用 reader 宏后,您将能够使用 #D。使用在其他语言中流行的点语法访问插槽名称。
(defclass person ()
((name :initarg :name :reader name)))
CL-USER> (access:enable-dot-syntax)
; No values
CL-USER> (defvar *foo* (make-instance 'person :name "John Smith"))
*FOO*
CL-USER> #D*foo*
#<PERSON #x302001F1E5CD>
CL-USER> #D*foo*.name
"John Smith"
如果您不想使用 reader 宏,还有一个 with-dot
宏
CL-USER> (access:with-dot () *foo*.name)
"John Smith"
您不应手动编写访问器,也不应使用 slot-value
(在对象生命周期函数之外,访问器可能尚未创建)。请改用 class 插槽选项:
(defclass foo ()
((name :reader foo-name
:initarg :name)
(bar :accessor foo-bar
:initarg :bar)))
现在您可以使用命名访问器了:
(defun example (some-foo new-bar)
(let ((n (foo-name some-foo))
(old-bar (foo-bar some-foo)))
(setf (foo-bar some-foo) new-bar)
(values n old-bar)))
通常,您希望 class 是 "immutable",您会使用 :reader
而不是 :accessor
,这只会创建 reader, 而不是 setf 扩展。
访问 class 个槽时,而不是写入
(defmethod get-name ((somebody person) (slot-value somebody 'name))
是否可以使用点符号 aka C++,即
(defmethod get-name ((somebody person) somebody.name) ?
否则,当一个方法中有很多插槽操作时,(slot-value...
会创建很多样板代码。
我今天已经找到了答案,我只是将其作为问答发布,但是如果有更好的解决方案或者我的解决方案有问题,请随时添加新的答案或评论。
最简单的解决方案似乎是 reader 重载 .
的宏,这样 (slot-value somebody 'name)
可以写成 .somebody.name
我的策略是阅读 somebody.name
作为一个字符串(我们需要定义一个非终止宏字符,这样 reader 就不会在字符串中间停止),然后处理该字符串以构造适当的 (slot-value...
我需要两个辅助函数:
(defun get-symbol (str)
"Make an uppercase symbol"
(intern (string-upcase str)))
(defun split-string (str sep &optional (start 0))
"Split a string into lists given a character separator"
(let ((end (position sep str :start start)))
(cons (subseq str start end) (if end (split-string str sep (1+ end))))))
然后我可以定义我的 reader 宏:
(defun dot-reader (stream char)
(declare (ignore char))
(labels ((make-query (list)
(let ((car (car list))
(cdr (cdr list)))
(if cdr `(slot-value ,(make-query cdr) (quote ,(get-symbol car)))
(get-symbol car)))))
(make-query (nreverse (split-string (symbol-name (read stream)) #\.)))))
最后,我需要注册这个reader宏:
(set-macro-character #\. #'dot-reader t)
现在可以写:
(defmethod get-name ((somebody person) .somebody.name)
或者,如果 name
本身就是一个 class,
(defmethod get-name ((somebody person) .somebody.name.first-name)
一个限制是 s 表达式在点之间不起作用,比如说
.(get-my-class).name
不行。
库 access 提供了一个点符号 reader 宏来访问槽(以及哈希表和其他东西)。通过调用 (access:enable-dot-syntax) 启用 reader 宏后,您将能够使用 #D。使用在其他语言中流行的点语法访问插槽名称。
(defclass person ()
((name :initarg :name :reader name)))
CL-USER> (access:enable-dot-syntax)
; No values
CL-USER> (defvar *foo* (make-instance 'person :name "John Smith"))
*FOO*
CL-USER> #D*foo*
#<PERSON #x302001F1E5CD>
CL-USER> #D*foo*.name
"John Smith"
如果您不想使用 reader 宏,还有一个 with-dot
宏
CL-USER> (access:with-dot () *foo*.name)
"John Smith"
您不应手动编写访问器,也不应使用 slot-value
(在对象生命周期函数之外,访问器可能尚未创建)。请改用 class 插槽选项:
(defclass foo ()
((name :reader foo-name
:initarg :name)
(bar :accessor foo-bar
:initarg :bar)))
现在您可以使用命名访问器了:
(defun example (some-foo new-bar)
(let ((n (foo-name some-foo))
(old-bar (foo-bar some-foo)))
(setf (foo-bar some-foo) new-bar)
(values n old-bar)))
通常,您希望 class 是 "immutable",您会使用 :reader
而不是 :accessor
,这只会创建 reader, 而不是 setf 扩展。