使用点表示法访问CLOS插槽

时间:2016-08-27 16:31:31

标签: common-lisp clos

访问类槽时,而不是写

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

是否可以使用点符号aka C ++,即

(defmethod get-name ((somebody person) somebody.name) ?

否则,当方法中有许多插槽操作时,(slot-value...会创建大量的样板代码。

我今天已经找到了答案,我只是将其作为Q& A发布,但是如果有更好的解决方案或者我的解决方案存在问题,请随时添加新的答案或评论。

3 个答案:

答案 0 :(得分:5)

access提供了一个用于访问插槽(以及散列表和其他内容)的点符号读取器宏。通过调用(access:enable-dot-syntax)启用阅读器宏后,您将能够使用#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"

如果您不想使用阅读器宏,还有一个with-dot

CL-USER> (access:with-dot () *foo*.name)
"John Smith"

答案 1 :(得分:3)

You should not write accessors by hand, nor use slot-value (outside of object lifecycle functions, where the accessors may not have been created yet). Use the class slot options instead:

(defclass foo ()
  ((name :reader foo-name
         :initarg :name)
   (bar :accessor foo-bar
        :initarg :bar)))

Now you can use the named accessors:

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

Often, you want your classes to be "immutable", you'd use :reader instead of :accessor then, which only creates the reader, not the setf expansion.

答案 2 :(得分:2)

最简单的解决方案似乎是重载.的读者宏,以便(slot-value somebody 'name)可以写为.somebody.name我的策略是将somebody.name读为字符串(我们需要定义一个非终止的宏字符,以便读者不会停止中间字符串),然后处理字符串以构造适当的(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))))))

然后我可以定义我的阅读器宏:

(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

无效。