有人可以帮助我了解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
正确的位置?
答案 0 :(得分:3)
这样做似乎有点复杂。例如,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-able函数(假设symbol
指向列表列表):
(push 1 (first symbol))
扩展为
(LET* ((#:G909 1)
(#:SYMBOL908 SYMBOL)
(#:NEW907 (CONS #:G909 (FIRST #:SYMBOL908))))
(SB-KERNEL:%RPLACA #:SYMBOL908 #:NEW907))
因此,除非你花些时间研究setf
,setf expansions和公司,否则这看起来相当晦涩(甚至在研究它们之后仍然看起来如此)。 OnLisp中的“广义变量”一章也可能有用。
提示:如果您编译自己的SBCL(不那么难),请将--fancy
参数传递给make.sh
。这样您就可以快速查看SBCL中函数/宏的定义(例如,在Emacs + SLIME中使用 M - 。)。显然,不要删除这些来源(您可以在clean.sh
之后运行install.sh
,以节省90%的空间。)
答案 1 :(得分: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)))))
一些观察结果:
这似乎适用于setq
或setf
。根据你实际想要解决的问题(我认为重写push
不是实际的最终目标),你可能会偏向于其中一个。
请注意,place
仍然会被评估两次...尽管只有在评估element
之后才会这样做。双重评估是否真的需要避免? (鉴于内置的push
没有,我想知道你是否/如何能够......虽然我在写这篇文章之前花了很多时间思考它。)鉴于它需要评估为“place”,这可能是正常的吗?
使用let*
代替let
,我们可以在,el-sym
的设置中使用,new-sym
。这会移动cons
发生的位置,以便在,place
的第一次评估中以及,element
的评估之后进行评估。在评估订购方面,或许这可以满足您的需求?
我认为您的第二个版本的最大问题是您的setf
确实需要对传入的符号进行操作,而不是gensym
符号。
希望这会有所帮助......(我自己对这一切仍然有点新意,所以我在这里做一些猜测。)