2

有人可以帮我理解如何push实现为宏吗?下面的朴素版本对位置形式进行了两次评估,并且在评估元素形式之前这样做:

(defmacro my-push (element place)
  `(setf ,place (cons ,element ,place)))

但是,如果我尝试按以下方式解决此问题,那么我setf就在错误的地方:

(defmacro my-push (element place)
   (let ((el-sym    (gensym))
         (place-sym (gensym)))
     `(let ((,el-sym    ,element)
            (,place-sym ,place))
        (setf ,place-sym (cons ,el-sym ,place-sym)))))

CL-USER> (defparameter *list* '(0 1 2 3))
*LIST*
CL-USER> (my-push 'hi *list*)
(HI 0 1 2 3)
CL-USER> *list*
(0 1 2 3)

我怎样才能setf在不评估两次的情况下找到正确的地方?

4

2 回答 2

4

做到这一点似乎有点复杂。例如,pushSBCL 1.0.58 中的代码是:

(defmacro-mundanely push (obj place &environment env)
  #!+sb-doc
  "Takes an object and a location holding a list. Conses the object onto
  the list, returning the modified list. OBJ is evaluated before PLACE."
  (multiple-value-bind (dummies vals newval setter getter)
      (sb!xc:get-setf-expansion place env)
    (let ((g (gensym)))
      `(let* ((,g ,obj)
              ,@(mapcar #'list dummies vals)
              (,(car newval) (cons ,g ,getter))
              ,@(cdr newval))
         ,setter))))

因此阅读有关get-setf-expansion的文档似乎很有用。

作为记录,生成的代码看起来相当不错:

推入一个符号:

(push 1 symbol)

扩展到

(LET* ((#:G906 1) (#:NEW905 (CONS #:G906 SYMBOL)))
  (SETQ SYMBOL #:NEW905))

推入 SETF 功能(假设symbol指向列表列表):

(push 1 (first symbol))

扩展到

(LET* ((#:G909 1)
       (#:SYMBOL908 SYMBOL)
       (#:NEW907 (CONS #:G909 (FIRST #:SYMBOL908))))
  (SB-KERNEL:%RPLACA #:SYMBOL908 #:NEW907))

因此,除非您花一些时间来学习setfsetf 扩展和公司,否则这看起来相当神秘(即使在研究它们之后看起来仍然如此)。OnLisp中的“广义变量”一章可能也很有用。

提示:如果您编译自己的 SBCL(不是那么难),请将--fancy参数传递给make.sh. 通过这种方式,您将能够快速查看 SBCL 中的函数/宏的定义(例如,M-.在 Emacs+SLIME 中)。显然,不要删除那些源(你可以在clean.sh之后运行install.sh,以节省 90% 的空间)。

于 2012-08-10T06:25:55.640 回答
1

看看现有的(至少在 SBCL 中)是如何做事的,我看到:

* (macroexpand-1 '(push 1 *foo*))

(LET* ((#:G823 1) (#:NEW822 (CONS #:G823 *FOO*)))
  (SETQ *FOO* #:NEW822))
T

所以,我想,结合你的版本和它产生的东西,一个人可能会做:

(defmacro my-push (element place)
   (let ((el-sym  (gensym))
         (new-sym (gensym "NEW")))
     `(let* ((,el-sym  ,element)
             (,new-sym (cons ,el-sym ,place)))
        (setq ,place ,new-sym)))))

几点观察:

  1. 这似乎适用于setqor setf。根据您实际尝试解决的问题(我认为重写push不是实际的最终目标),您可能会偏爱其中一个。

  2. 请注意,place它仍然会被评估两次......尽管它至少在评估之后才会这样做element。你真的需要避免双重评估吗?(鉴于内置push没有,我想知道您是否/如何能够......尽管我在花很多时间思考之前写了这篇文章。)鉴于它是需要评价为“地方”,或许这很正常?

  3. 使用let*而不是let允许我们,el-sym在设置中使用,new-sym。这将移动到cons发生的位置,以便在 的第一次评估,place和 的评估之后对其进行评估,element。关于评估排序,也许这可以满足您的需求?

  4. 我认为您的第二个版本的最大问题是您setf确实需要对传入的符号进行操作,而不是对gensym符号进行操作。

希望这会有所帮助......(我自己对这一切还有些陌生,所以我在这里做一些猜测。)

于 2012-08-08T20:17:12.597 回答