如何为“递归”编写宏;槽值“;对于defstruct?

如何为“递归”编写宏;槽值“;对于defstruct?,struct,macros,lisp,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(见下文)。这种

问题:如何为析构函数(和实例)的嵌套插槽值编写通用访问器宏

动机:我是一名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
    宏扩展问题,就只需要一个
    get
我当前的代码:(有人能用一个defmacro替换它吗?)

(取消rslots获取(o l)
(如果(cdr l)
(rslots get(槽值o(车l))(cdr l))
(槽值o(轿厢l)))
(去风机座组(o l z)
(setf)
(槽值o(轿厢l))
(如果(cdr l)
(rslots集合(槽值o(左车))(cdr l)z)
z) )
o)
(卸下rslots推力(o l z)
(setf)
(槽值o(轿厢l))
(如果(cdr l)
(rslots推送(插槽值o(左车))(cdr l)z)
(推z(槽值o(轿厢l‘‘‘)’)
o)
(左、右、左、右)
(defmacro!!(oz&restl)`(rslots集,o',l,z))

(defmacro您只需编写一个简单的宏,将其扩展到嵌套的
槽值
s。扩展到某个位置的宏本身就是一个有效的位置,因此您无需担心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)
(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
您也不能在宏中引用插槽名称,以便在运行时计算它们

(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扩展

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

这将把要修改的对象存储在一个变量中,这样修改宏就不需要再查找它两次。

您只需编写一个简单的宏,将其展开为嵌套的
槽值。
s。展开到某个位置的宏本身就是一个有效的位置,因此您无需担心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)
(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
您也不能在宏中引用插槽名称,以便在运行时计算它们

(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扩展

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

这将把要修改的对象存储在一个变量中,这样修改宏就不需要再查找它两次。

您可以使用递归宏。基本情况是一个单参数,它只转换为调用
插槽值
。否则,它只需使用额外的插槽就可以递归调用自身

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

您可以使用递归宏。基本情况是单个参数,它只转换为调用
插槽值
。否则,它只使用附加插槽递归调用自身

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

正是TXR Lisp中的情况。经过反思,我意识到我需要处理两种情况。情况1:插槽在加载时已知——在这种情况下,
是完美的解决方案。情况2是在运行时找到插槽。对于这种情况,我意识到我只需要一个函数(而不是上面显示的其他三个函数)。具体来说,我可以用一个函数替换上面的所有代码,该函数将更新lambda主体向下传递到插槽(该代码协调如何进行更改)。若要查看该代码,请浏览@TimMenzies。如果您想在运行时确定插槽,您可以从宏中删除引号,而在使用它时引用常量插槽名称。这不是稍微多了一点吗?如果您想更新插槽,您必须将每个递归调用包装在一个setf中(因此更新的子对象返回到父对象)?请参阅中的
更改
。您只修改了最后一个插槽。无需设置其他插槽,因为它们不会更改。这只是不可变数据结构的问题(在这种情况下,您可以使用
DEFINE-setf-expander
编写更复杂的setf扩展器)。这正是TXR Lisp中的情况。经过反思,我意识到我需要处理两种情况。情况1:插槽在加载时已知——在这种情况下,
是完美的解决方案。情况2是在运行时找到插槽。对于这种情况,我意识到我只需要一个函数(而不是上面显示的其他三个函数)。具体来说,我可以用一个函数替换上面的所有代码,该函数将更新lambda主体向下传递到插槽(该代码协调如何进行更改)。若要查看该代码,请浏览@TimMenzies。如果您想在运行时确定插槽,您可以从宏中删除引号,而在使用它时引用常量插槽名称。这不是稍微多了一点吗?如果您想更新插槽,您必须将每个递归调用包装在一个setf中(因此更新的子对象返回到父对象)?请参阅中的
change
。您只修改了最后一个插槽。无需设置其他插槽,因为它们不会更改。这只是不可变数据结构的问题(在这种情况下,您可以使用
DEFINE-setf-expander
编写更复杂的setf扩展器)。