Common lisp 使用点表示法访问CLOS插槽

Common lisp 使用点表示法访问CLOS插槽,common-lisp,clos,Common Lisp,Clos,当访问类槽时,而不是写入 (defmethod get-name ((somebody person) (slot-value somebody 'name)) 有可能使用点符号AKA C++,即 (defmethod get-name ((somebody person) somebody.name) ? 否则,当一个方法中有许多槽操作时,(槽值…会创建大量样板代码 我今天已经找到了答案,我只是把它作为一个问答发布,但是如果有更好的解决方案或者我的解决方案存在问题,请随意添加新的答案或评论

当访问类槽时,而不是写入

(defmethod get-name ((somebody person) (slot-value somebody 'name))

有可能使用点符号AKA C++,即

(defmethod get-name ((somebody person) somebody.name) ?
否则,当一个方法中有许多槽操作时,
(槽值…
会创建大量样板代码


我今天已经找到了答案,我只是把它作为一个问答发布,但是如果有更好的解决方案或者我的解决方案存在问题,请随意添加新的答案或评论。

最简单的解决方案似乎是一个读卡器宏,它重载
,以便
(插槽值某人的姓名)
可以写为
.someone.name
我的策略是将
someone.name
作为字符串读取(我们需要定义一个非终止宏字符,以便读取器不会停止中间字符串),然后处理该字符串以构造适当的
(插槽值…

我需要两个助手函数:

(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))))))
然后我可以定义我的读卡器宏:

(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)) #\.)))))
(set-macro-character #\. #'dot-reader t)
最后,我需要注册这个读卡器宏:

(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)) #\.)))))
(set-macro-character #\. #'dot-reader t)
现在可以写:

(defmethod get-name ((somebody person) .somebody.name)
或者,如果
name
本身就是一个类

(defmethod get-name ((somebody person) .somebody.name.first-name)
比如说,一个限制是s表达式在点之间不起作用

.(get-my-class).name
无法工作。

该库提供了一个点符号读取器宏,用于访问插槽(以及哈希表和其他内容)。通过调用(访问:启用点语法)启用读取器宏后,您将能够使用#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"

您不应手动写入访问者,也不应使用
插槽值
(在对象生命周期函数之外,可能尚未创建访问者)。请改用类插槽选项:

(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)))

通常,你希望你的类是“不可变的”,你会使用
:reader
而不是
:accessor
,它只创建reader,而不是setf扩展。

这是否仍然允许正常的考虑点,例如
(setq cons'(a.b))
?@Barmar有趣的一点是,考虑到点仍然有效。在本例中,它只是不调用reader宏。感谢您指出此库。不幸的是,我现在无法尝试,(ql:quickload“access”)会导致错误(我将很难记住pacman-S上次导致错误的时间)。解决这些问题后,我将返回您的答案。显然,在前向兼容性方面存在一些问题。使用(ql:update all Dist)更新所有包解决了这个问题,我现在可以试试access库。我喜欢它,因为它是一个库,我自己不需要编码和维护任何东西。我不太喜欢手头的任务。该库的重点不同于简单地为类提供点表示法,它做了大量的运行时检查。访问对象的插槽是一个简单的过程时间比我的手动方法慢,访问对象插槽中的插槽慢174倍。谢谢,这确实是我要找的。我想我只是没有足够多地阅读实用公共Lisp中的相应章节:),所以我也不知道有带插槽和带访问器的宏。