宏参数未被替换
我试图完全理解编译时宏的局限性。
这是一个宏(我完全意识到这不是最佳实践宏):
(defmacro emit (language file &body body)
(print language)
(print file)
(print body)
(with-open-file (str file :direction :output :if-exists :supersede)
(princ (cond ((eq language 'html)
(cl-who:with-html-output-to-string (s nil :prologue t :indent t) body))
((eq language 'javascript)
(parenscript:ps body))
((eq language 'json)
(remove #\; (parenscript:ps body))))
str)))
我编译宏:
; processing (DEFMACRO EMIT ...)
PROGRAM>
我编译这种形式:
PROGRAM> (compile nil (lambda () (emit json "~/file" (ps:create "hi" "hello") (ps:create "yo" "howdy"))))
JSON
"~/file"
((PARENSCRIPT:CREATE "hi" "hello") (PARENSCRIPT:CREATE "yo" "howdy"))
#<FUNCTION (LAMBDA ()) {5367482B}>
NIL
NIL
PROGRAM>
编译时print
输出是我所期望的。
但是,如果我查看 ~/file
:
body
看来 ((PARENSCRIPT:CREATE "hi" "hello") (PARENSCRIPT:CREATE "yo" "howdy"))
从未被替换为参数 body
,因此从未被处理。
这是为什么呢?
&关于这个主题最好的文献是什么?
I'm trying to fully understand the limitations of compile-time macros.
Here is a macro (I'm fully aware that this is not a best-practice macro):
(defmacro emit (language file &body body)
(print language)
(print file)
(print body)
(with-open-file (str file :direction :output :if-exists :supersede)
(princ (cond ((eq language 'html)
(cl-who:with-html-output-to-string (s nil :prologue t :indent t) body))
((eq language 'javascript)
(parenscript:ps body))
((eq language 'json)
(remove #\; (parenscript:ps body))))
str)))
I compile the macro:
; processing (DEFMACRO EMIT ...)
PROGRAM>
I compile this form:
PROGRAM> (compile nil (lambda () (emit json "~/file" (ps:create "hi" "hello") (ps:create "yo" "howdy"))))
JSON
"~/file"
((PARENSCRIPT:CREATE "hi" "hello") (PARENSCRIPT:CREATE "yo" "howdy"))
#<FUNCTION (LAMBDA ()) {5367482B}>
NIL
NIL
PROGRAM>
The compile-time print
output is what I expect.
However, if I look at ~/file
:
body
It appears that ((PARENSCRIPT:CREATE "hi" "hello") (PARENSCRIPT:CREATE "yo" "howdy"))
was never substituted in for the parameter body
, and thus never processed.
Why is this?
& what would be the best literature to read on this subject?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
为什么要用它来替代呢?你从来没有替代过任何东西。
宏定义了宏替换函数,该函数应用于代码中的实际形式以生成另一种形式,然后进行编译。当您将宏定义应用于这些参数时,它会在宏扩展时执行各种操作(写入文件等),然后返回
princ
返回的内容,这正是它的第一个参数,然后编译返回的形式。我不认为那是你想要的。看来您真正想做的是扩展到一种以多种方式解释身体的形式,如第一个参数所示。
您需要做的是返回新表单,以便
扩展为
为此,我们有一个模板语法:反引号。
含义相同
,但使转换后的代码结构更清晰一些。还有
,@
可以将事物拼接到列表中。和i的意思一样
。 e.
bar
的内容,当它们是列表时,会被拼接到列表中。这对于诸如宏之类的主体特别有用。请注意我在哪里引入了反引号来创建模板和逗号以将外部参数放入其中。另请注意,参数是形式。
这有一些问题:存在宏用户无法知道的硬编码符号。在一种情况下(
str
),他们必须注意不要隐藏它,在另一种情况下(s
),他们必须知道它才能写入它。为此,我们要么使用生成的符号(对于str
,这样就不会发生冲突),或者让用户说出他们想要的名称(对于s
)。此外,此cond
可以简化为case
:但是,您可能希望在宏扩展时确定输出代码。
在这里,您可以看到
case
表单已经在宏扩展时进行了评估,然后使用内部模板来创建内部表单。这一切都完全未经测试,因此消除小错误作为练习^^。
Paul Graham 所著的《论 Lisp》是一本对宏编写有很多论述的书。 Peter Seibel 的免费版《Practical Common Lisp》也有一章介绍它,Edi Weitz 的《Common Lisp Recipes》中也有一些食谱。
Why should it substitute? You never substituted anything.
A macro defines a macro substitution function, which is applied to the actual form in the code to produce another form which is then compiled. When you apply your macro definition to those parameters, it will at macroexpansion time do all kinds of things (write a file etc.) before returning what
princ
returned, which is exactly its first argument, and this returned form is then compiled. I don't think that is what you want.It seems that what you actually want to do is to expand to a form that interprets the body in one of a variety of ways, indicated by the first argument.
What you need to do is to return the new form, so that
expands to
For that, we have a template syntax: the backtick.
means the same as
but makes the structure of transformed code a bit clearer. There is also
,@
to splice things into a list.means the same as
i. e. the contents of
bar
, when they are a list, are spliced into the list. This is especially useful for bodies such as in your macro.Note where I introduced the backtick to create a template and commata to put outer arguments into it. Note also that the arguments are forms.
This has a few problems: there are hardcoded symbols that the user of the macro has no way of knowing. In one case (
str
) they have to pay attention not to shadow it, in the other (s
) they have to know it in order to write to it. For this, we use either generated symbols (forstr
so that there is no conflict possible) or let the user say what they want to name it (fors
). Also, thiscond
can be simplified to acase
:However, you might want to determine the output code already at macro expansion time.
Here, you can see that the
case
form is already evaluated at macro expansion time, and an inner template is then used to create the inner form.This is all completely untested, so removing the little errors is left as an exercise ^^.
One book that has a lot of things to say about macro writing is »On Lisp« by Paul Graham. The freely available »Practical Common Lisp« by Peter Seibel also has a chapter about it, and there are also some recipes in »Common Lisp Recipes« by Edi Weitz.
parenscript:ps
是一个宏,而不是一个函数:它的主体是字面 parenscript,不进行计算,而是进行编译,从 Parenscript 到 JavaSctipt。这很容易检查:我对您应该阅读的内容没有任何建议:这个宏看起来非常混乱,我无法真正理解潜在的意图是什么。 CL 中的宏是一个函数,其参数是某种语言 L1 的源代码,并返回某种语言 L2 的源代码,其中 L2 通常是 L1 的子集。不过,我无法弄清楚,这是否只是有人在需要函数时认为自己需要宏的正常情况,或者是否有其他混乱。
parenscript:ps
is a macro, not a function: its body is literal parenscript and is not evaluated but compiled, from Parenscript to JavaSctipt. This is easy to check:I don't have any advice on what you should read: this macro looks so utterly confused I can't really understand what the underlying intent was. A macro in CL is a function which whose argument is source code in some language L1 and which returns source code in some language L2, where L2 is usually a subset of L1. I can't work out, though, if this is just the normal case of someone thinking they need a macro when they need a function, or if it's some other confusion.