Macros 如何实现push宏?
有人能帮我理解如何将Macros 如何实现push宏?,macros,common-lisp,Macros,Common Lisp,有人能帮我理解如何将push实现为宏吗?下面的朴素版本对place表单求值两次,并在求值元素表单之前执行此操作: (defmacro my-push (element place) `(setf ,place (cons ,element ,place))) 但如果我尝试按以下方式修复此问题,那么我将设置到错误的位置: (defmacro my-push (element place) (let ((el-sym (gensym)) (place-sym (g
push
实现为宏吗?下面的朴素版本对place表单求值两次,并在求值元素表单之前执行此操作:
(defmacro my-push (element place)
`(setf ,place (cons ,element ,place)))
但如果我尝试按以下方式修复此问题,那么我将设置到错误的位置:
(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)
我如何才能在不进行两次评估的情况下设置正确的位置?看看现有的一个(至少在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)))))
一些意见:
这似乎适用于setq
或setf
。根据您实际试图解决的问题(我认为重新编写push
不是实际的最终目标),您可能会选择其中一个
请注意,place
仍然会得到两次评估。。。尽管它至少在计算元素
之后才这样做。你真的需要避免双重评估吗?(考虑到内置的推送功能不起作用,我想知道你是否/如何能够……尽管我在花大量时间思考之前写了这篇文章。)考虑到这是一个需要评估为“”的东西,也许这是正常的
使用let*
而不是let
允许我们在设置、新sym
时使用、el sym
。这将移动到cons
发生的位置,以便在的第一次求值中,放置,以及在的求值之后,元素
。关于评估顺序,这可能会满足您的需要
我认为第二个版本的最大问题是,setf
确实需要对传入的符号进行操作,而不是对gensym
符号进行操作
希望这有助于。。。(我自己对这一切还有些陌生,所以我在这里做一些猜测。)正确地做这件事似乎有点复杂。例如,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))))
因此,阅读上的文档似乎很有用
作为记录,生成的代码看起来相当不错:
推入一个符号:
(push 1 symbol)
扩展到
(LET* ((#:G906 1) (#:NEW905 (CONS #:G906 SYMBOL)))
(SETQ SYMBOL #:NEW905))
(LET* ((#:G909 1)
(#:SYMBOL908 SYMBOL)
(#:NEW907 (CONS #:G909 (FIRST #:SYMBOL908))))
(SB-KERNEL:%RPLACA #:SYMBOL908 #:NEW907))
推入可设置的功能(假设symbol
指向列表列表):
扩展到
(LET* ((#:G906 1) (#:NEW905 (CONS #:G906 SYMBOL)))
(SETQ SYMBOL #:NEW905))
(LET* ((#:G909 1)
(#:SYMBOL908 SYMBOL)
(#:NEW907 (CONS #:G909 (FIRST #:SYMBOL908))))
(SB-KERNEL:%RPLACA #:SYMBOL908 #:NEW907))
所以,除非你花一些时间去研究setf和它的公司,否则这看起来相当神秘(即使在研究它们之后也可能如此)。中的“广义变量”一章也可能有用
提示:如果编译自己的SBCL(没有那么难),请将--fancy
参数传递给make.sh
。通过这种方式,您将能够快速查看SBCL中函数/宏的定义(例如,使用Emacs+SLIME中的M-.in)。显然,不要删除这些源代码(您可以在install.sh
之后运行clean.sh
,以节省90%的空间)。查看SBCL源代码,我发现它们使用一次性宏来处理rplaca(有关更多信息,请参阅此链接)。我会调查rplaca以进一步了解。@PaulNathan您能提供您正在查看的SBCL版本和源位置吗?因为我在src/code/early setf.lisp
(SBCL 1.0.58)中看到了,它不会只使用一次,
。很有趣。HyperSpec说位置表单只计算一次,但看起来SBCL知道从符号中获取两次值是无害的。对于可能有副作用的位置表单,它会:CL-USER>(宏扩展-1'(push 4(my list func))(LET*(#:G31 4)(#:G30(CONS):G31(MY-LIST-FUNC)))#:G30(FUNCALL#’(SETF MY-LIST-FUNC)#:G30))。这种形式的SETF是否以某种方式避免了第二次求值?当我实际运行上述函数时,我得到了“Undefined function(SETF MY-LIST-FUNC)”。看起来,通过禁止无法自行设置的place表单,避免了双重求值问题。这很有意义。