如何实现推送宏?

问题描述 投票:0回答:3

有人可以帮我理解如何将

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
找到正确的地方?

macros common-lisp
3个回答
4
投票

正确执行此操作似乎有点复杂。例如,SBCL 1.0.58 中

push
的代码是:

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

因此,除非您花一些时间来研究

setf
setf 扩展 和公司,否则这看起来相当神秘(即使在研究它们之后,它可能仍然看起来如此)。 OnLisp 中的“广义变量”章节也可能有用。

提示:如果您编译自己的 SBCL(没那么难),请将

--fancy
参数传递给
make.sh
。这样您就可以快速查看 SBCL 中函数/宏的定义(例如,Emacs+SLIME 中的 M-.)。显然,不要删除这些源(您可以在
clean.sh
之后运行
install.sh
,以节省 90% 的空间)。


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. 这似乎适用于

    setq
    setf
    。根据您实际想要解决的问题(我认为重写
    push
    不是实际的最终目标),您可能会偏向其中一个。

  2. 请注意,

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

  3. 使用

    let*
    代替
    let
    可以让我们在
    ,el-sym
    的设置中使用
    ,new-sym
    。这会移动
    cons
    发生的位置,以便在第一次评估
    ,place
    时以及在评估
    ,element
    后对其进行评估。也许这可以满足您在评估订购方面的需求?

  4. 我认为你的第二个版本的最大问题是你的

    setf
    确实需要对传入的符号进行操作,而不是对
    gensym
    符号进行操作。

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


0
投票

为什么这个宏不够用? (defmacro my-push(元素位置) `(setf,地点(缺点,元素,地点))) 它的工作原理与(推)相同

CL-USER>(默认参数list'(0 1 2 3)) 列表 CL-USER> (my-push 'hi list) (高0 1 2 3) CL-USER>(我的推“零”(第二个列表)) (“零”。0) CL-用户> 列表 (HI(“零”.0)1 2 3) CL-USER>(推“一”(第三个列表));只需推 (“一”。1) CL-用户> 列表 (HI (“零”. 0) (“一”. 1) 2 3) CL-用户>

© www.soinside.com 2019 - 2024. All rights reserved.