如何实现推宏? [英] How could I implement the push macro?
问题描述
有人可以帮助我了解如何将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
正确的位置?
做到这一点似乎要复杂一些.例如,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 上的文档似乎很有用. /p>
记录下来,生成的代码看起来很不错:
插入符号:
(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-.).显然,不要删除这些源(可以在install.sh
之后运行clean.sh
,以节省90%的空间).
Can someone help me understand how push
can be implemented as a macro? The naive version below evaluates the place form twice, and does so before evaluating the element form:
(defmacro my-push (element place)
`(setf ,place (cons ,element ,place)))
But if I try to fix this as below then I'm setf
-ing the wrong 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)
How can I setf
the correct place without evaluating twice?
Doing this right seems to be a little more complicated. For instance, the code for push
in SBCL 1.0.58 is:
(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))))
So reading the documentation on get-setf-expansion seems to be useful.
For the record, the generated code looks quite nice:
Pushing into a symbol:
(push 1 symbol)
expands into
(LET* ((#:G906 1) (#:NEW905 (CONS #:G906 SYMBOL)))
(SETQ SYMBOL #:NEW905))
Pushing into a SETF-able function (assuming symbol
points to a list of lists):
(push 1 (first symbol))
expands into
(LET* ((#:G909 1)
(#:SYMBOL908 SYMBOL)
(#:NEW907 (CONS #:G909 (FIRST #:SYMBOL908))))
(SB-KERNEL:%RPLACA #:SYMBOL908 #:NEW907))
So unless you take some time to study setf
, setf expansions and company, this looks rather arcane (it may still look so even after studying them). The 'Generalized Variables' chapter in OnLisp may be useful too.
Hint: if you compile your own SBCL (not that hard), pass the --fancy
argument to make.sh
. This way you'll be able to quickly see the definitions of functions/macros inside SBCL (for instance, with M-. inside Emacs+SLIME). Obviously, don't delete those sources (you can run clean.sh
after install.sh
, to save 90% of the space).
这篇关于如何实现推宏?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!