问题:如何为析构(以及实例)的嵌套槽值编写通用访问器宏?
动机:我是一名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(见下文)。这种方法有局限性:
get
和set
编写单独的函数,而如果我可以解决setf
宏扩展问题,那么只需要一个{{1} }}。我当前的代码 :(任何人都可以用一个defmacro替换它吗?)
get
ADVAthanxNCE!
答案 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)))