我怎么能实现推动宏?(How could I implement the push macro?)

2019-09-21 00:18发布

有人可以帮助我了解如何push可作为宏来实现? 下面的幼稚版本评估地方形式两次,评估元素形式之前这样做:

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

但是,如果我试图解决这个问题,如下那么我setf -ing错了地方:

(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不计算两次正确的地方吗?

Answer 1:

这样做的权利似乎是一个更复杂些。 例如,对于代码push在SBCL 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膨胀似乎是有用的。

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

推成一个符号:

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

所以,除非你需要一些时间来研究setf , SETF扩张和公司,这看起来相当神秘的(但仍可能显得那么即使他们学习后)。 在“广义变量”一章OnLisp可能是有用的。

提示:如果您编译自己的SBCL(并不难),通过--fancy参数make.sh 。 这样,您就可以快速地看到里面SBCL功能/宏的定义(例如,与M-。Emacs的+泥里面)。 显然,不删除这些源(可以运行clean.shinstall.sh ,节省90%的空间)。



Answer 2:

考虑看看如何在现有的(在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. 这似乎与任何工作setqsetf 。 根据什么问题,你实际上是试图解决(我相信重写push是不实际的最终目标),你可能会偏向一方或另一方。

  2. 需要注意的是place确实还得到两次评估...只有评估后尽管它至少这样做的element 。 是你真正需要避免双重评价的东西吗? (由于内置push不,我在想,如果/你怎么能...虽然我花非常多的时间考虑这个问题。之前写这个了),考虑到它的东西需要评价为“ 到位 ”,也许这是正常的吗?

  3. 使用let*而不是let允许我们使用,el-sym中的设置,new-sym 。 这使其中的cons情况,例如,它在第一评价评估,place ,和后评价,element 。 或许,这可以让你你需要什么,相对于评估排序?

  4. 我认为你的第二个版本,最大的问题是,你的setf确实需要传递的符号操作,而不是在一个gensym符号。

希望这有助于...(我还是有点新的这一切我自己,所以我在这里做一些猜测。)



文章来源: How could I implement the push macro?