如何为递归" slot-value"写一个宏? for defstruct?

时间:2017-10-27 19:23:45

标签: struct macros lisp

问题:如何为析构(以及实例)的嵌套槽值编写通用访问器宏?

动机:我是一名LISP程序员,羡慕"点符号"在Python等,嵌套插槽访问只需几个点

用例:我想要做的是

(print (?? obj a b c) 
; i.e. ((print (slot-value (slot-value (slot-value obj 'a) 'b ) 'c))
(setf (?? obj a b c) newValue)

当前结果(不合适):我能做的最好的是一些嵌套的defuns(见下文)。这种方法有局限性:

  • 那些运行时间,理想情况下,所有嵌套访问器工作都在加载时发生。
  • 我必须为getset编写单独的函数,而如果我可以解决setf宏扩展问题,那么只需要一个{{1} }}。

我当前的代码 :(任何人都可以用一个defmacro替换它吗?)

get

ADVAthanxNCE!

2 个答案:

答案 0 :(得分:2)

您可以编写一个扩展为嵌套SLOT-VALUE的简单宏。扩展到某个位置的宏本身就是一个有效的位置,因此您无需担心setf扩展。

(defmacro ?? (object &rest slots)
  (reduce (lambda (acc slot)
            `(slot-value ,acc ',slot))
          slots
          :initial-value object))

(macroexpand '(?? obj a b c))
;=> (SLOT-VALUE (SLOT-VALUE (SLOT-VALUE OBJ 'A) 'B) 'C)

您也可以不引用宏中的插槽名称以允许在运行时计算它们。

(defmacro ?? (object &rest slots)
  (reduce (lambda (acc slot)
            `(slot-value ,acc ,slot))
          slots
          :initial-value object))

(macroexpand '(?? obj 'a 'b 'c))
;=> (SLOT-VALUE (SLOT-VALUE (SLOT-VALUE OBJ 'A) 'B) 'C)

(macroexpand '(?? obj 'a var 'b))
;=> (SLOT-VALUE (SLOT-VALUE (SLOT-VALUE OBJ 'A) VAR) 'B)

之前的假设是您知道插槽的数量。如果不这样做,那么你将不得不使用一个函数。

(defun ? (object slot &rest more-slots)
  (reduce (lambda (obj slot)
            (slot-value obj slot))
          more-slots
          :initial-value (slot-value object slot)))

(defun (setf ?) (new-value object slot &rest more-slots)
  (loop :for (slot . tail) :on (cons slot more-slots)
        :with acc := object
        :if (null tail) ;SLOT is the last slot in the list.
          :return (setf (slot-value acc slot) new-value)
        :else
          :do (setf acc (slot-value acc slot))))

(defstruct zzzz z1 (z2 0) (z3))
(defstruct yyyy y1 y2 (y3 (make-zzzz)))
(defstruct xxxx x1 x2 (x3 (make-yyyy)))

(defvar *xxxx* (make-xxxx))

(? *xxxx* 'x3 'y3 'z2) ;=> 0
(incf (? *xxxx* 'x3 'y3 'z2))
(? *xxxx* 'x3 'y3 'z2) ;=> 1

(setf (apply #'? *xxxx* '(x3 y3 z2)) 100)
(? *xxxx* 'x3 'y3 'z2) ;=> 100

这种方法效率稍低,因为修改地点需要两次遍历插槽。您可以使用DEFINE-SETF-EXPANDER来编写更高效的setf扩展。

(defun ??? (object slots)
  (reduce (lambda (obj slot)
            (slot-value obj slot))
          slots
          :initial-value object))

(define-setf-expander ??? (object slots)
  (with-gensyms (slots-temp last-slot-temp obj target store) ;From Alexandria (or elsewhere).
    (values (list slots-temp
                  last-slot-temp
                  obj
                  target)
            `(,slots
              (first (last ,slots-temp))
              ,object
              (??? ,obj (butlast ,slots-temp)))
            (list store)
            `(setf (slot-value ,target ,last-slot-temp) ,store)
            `(slot-value ,target ,last-slot-temp))))

(let ((slots '(x3 y3 z2)))
  (setf (??? *xxxx* slots) 5)
  (incf (??? *xxxx* slots)))
;=> 6

这会将要修改的对象存储在变量中,这样修改宏就不需要查找两次了。

答案 1 :(得分:2)

您可以使用递归宏。基本案例是单个参数,它只是转换为slot-value的调用。否则它只用额外的插槽递归调用它自己。

(defmacro ?? (obj first-slot &rest more-slots)
  (if (null more-slots)
      `(slot-value ,obj ',first-slot)
      `(?? (slot-value ,obj ',first-slot) ,@more-slots)))