4(根据Rainer Joswig的反馈进行修改):
(defmacro with-gensyms ((&rest names) &body body)
`(let ,(loop for n in names collect `(,n (gensym)))
,@body))
(defmacro let-alist ((&rest lookups) alist &body body)
(with-gensyms (alist-form)
`(let ((,alist-form ,alist))
(let ,(loop for l in lookups
if (keywordp l)
collect `(,(intern (symbol-name l)) (cdr (assoc ,l ,alist-form)))
else
collect `(,l (cdr (assoc ',l ,alist-form))))
,@body))))
这个修订版仍然可以用同样的方式使用。
> (defparameter test '((|foo| . 1) (:bar . 2) (baz . 3)))
TEST
> (let-alist (:bar |foo| baz) test
(list |foo| bar baz))
(1 2 3)
但是宏只展开传入的alist
表单一次。以下是slime-macroexpand-1
的输出:
(LET ((#:G1299 TEST))
(LET ((BAR (CDR (ASSOC :BAR #:G1299)))
(|foo| (CDR (ASSOC '|foo| #:G1299)))
(BAZ (CDR (ASSOC 'BAZ #:G1299))))
(LIST |foo| BAR BAZ)))
欢迎所有评论,但具体而言(按重要性下降的顺序排列),
发布于 2012-04-14 05:53:07
它的主要问题是alist
- -宏的变量。
CL-USER > (pprint (macroexpand-1 '(let-alist (:bar |foo| baz)
(this-function-takes-a-long-time-or-has-side-effects)
(list |foo| bar baz))))
(LET ((BAR (CDR (ASSOC :BAR (THIS-FUNCTION-TAKES-A-LONG-TIME-OR-HAS-SIDE-EFFECTS))))
(|foo|
(CDR (ASSOC '|foo| (THIS-FUNCTION-TAKES-A-LONG-TIME-OR-HAS-SIDE-EFFECTS))))
(BAZ
(CDR (ASSOC 'BAZ (THIS-FUNCTION-TAKES-A-LONG-TIME-OR-HAS-SIDE-EFFECTS)))))
(LIST |foo| BAR BAZ))
如您所见,函数被多次展开到代码中。
规则1:始终检查扩展是否有问题。
规则2:不要将相同的代码多次展开到代码中。
阅读Paul的“On”(免费下载),了解公共Lisp中宏的各种缺陷,以及如何处理这些缺陷。
对于上面的内容,您需要生成一个新的唯一变量并绑定一次值。
发布于 2012-04-14 10:02:54
这里有一个实现,它允许对作者中的值进行读和写访问。
首先,定义一个使用符号-宏的宏,其中主体中的绑定符号展开为在运算符中访问该符号的值的代码:
(defmacro with-alist% (alist-entries instance-form &body body)
`(symbol-macrolet
,(loop for (alist-binding alist-entry) in alist-entries
collect `(,alist-binding (cdr (assoc ',alist-entry ,instance-form))))
,@body))
并测试它:
(defparameter *al* (list (cons 'foo 5) (cons 'bar 'a) (cons 'baz "z")))
*AL*
CL-USER>
(print *al*)
((FOO . 5) (BAR . A) (BAZ . "z"))
((FOO . 5) (BAR . A) (BAZ . "z"))
CL-USER>
(with-alist% ((foo foo) (bar bar)) *al*
(format t "foo=~a, bar=~a~%" foo bar)
(setf foo 1)
(setf bar "a") ...)
foo=5, bar=A
foo=1, bar=a
NIL
CL-USER>
(print *al*)
((FOO . 1) (BAR . "a") (BAZ . "z"))
((FOO . 1) (BAR . "a") (BAZ . "z"))
CL-USER>
请注意,不仅可以使用绑定符号进行打印,还可以使用setf符号,这将映射到更改与该符号相关的值。这是使用符号-宏(而不是let)提供的。另外,如果您阅读了具有-槽的源代码,至少对于CCL,符号-宏是如何实现该宏的。再一次,我正致力于一种类似的-插槽句法感觉。
如果您有一个混合关键字和符号的列表,with- but %的效果就足够好了,但是我认为大多数情况下只有符号,这意味着在使用该函数时会有大量的代码重复,例如:
(with-alist% ((foo foo) (bar bar)) *al*
请注意如何列出每个列表条目两次。如果你希望绑定符号和列表条目总是一样的话,该怎么办?如果你能这样做就太好了:
(with-alist (foo bar) *al*
而这个宏:
(defmacro with-alist (alist-entries instance-form &body body)
`(with-alist% ,(mapcar (lambda (alist-entry)
(if (consp alist-entry)
`,alist-entry
`,(list alist-entry alist-entry)))
alist-entries)
,instance-form
,@body))
用法:
(print *al*)
CL-USER>
((FOO . 1) (BAR . "a") (BAZ . "z"))
((FOO . 1) (BAR . "a") (BAZ . "z"))
CL-USER>
(with-alist (foo (bar% bar)) *al*
(format t "foo=~a, bar=~a~%" foo bar%)
(setf foo 5)
(setf bar% 'a) ...)
foo=1, bar=a
foo=5, bar=A
NIL
CL-USER>
(print *al*)
((FOO . 5) (BAR . A) (BAZ . "z"))
((FOO . 5) (BAR . A) (BAZ . "z"))
CL-USER>
REPL
我同意Rainer关于只评估实例-表单一次的评论,当您只想要读取访问权限时。但是,如果您想要进行读/写访问,那么每次在正文中找到绑定符号时,都需要展开实例窗体。否则,您将写入实例-表单的let绑定;而不是实例-表单;因此,实例-表单不会更改。
对于通用Lisp宏编程来说,一个很好的阅读绝对是在Lisp上,而且还可以在Lambda上进行。通过LOL是一项重要的时间投资,但如果你想提高你常用的lisp宏编程技能的话,这是很值得的。
https://codereview.stackexchange.com/questions/10843
复制