宏展开后的未定义函数
Undefined function after macroexpansion
我正在学习 Common Lisp,想玩玩 lisp 和 web 开发。我当前的问题来自一个简单的想法,即遍历我想要包含的所有 javascript 文件。我使用 SBCL 和 Quicklisp 来快速启动。问题可能与我正在使用的 cl-who
包有关。
所以我已经声明了我的包裹并开始这样:
(defpackage :0xcb0
(:use :cl :cl-who :hunchentoot :parenscript))
(in-package :0xcb0)
为了简单起见,我减少了我的问题函数。所以我有这个 page
函数:
(defun page (test)
(with-html-output-to-string
(*standard-output* nil :prologue nil :indent t)
(:script
(:script :type "text/javascript" :href test))))
这将产生所需的输出
*(0xcb0::page "foo")
<script>
<script type='text/javascript' href='foo'></script>
</script>
现在我已经创建了一个生成 :script
个标签的宏。
(defmacro js-source-file (filename)
`(:script :type "text/javascript" :href ,filename)))
这按预期工作:
*(macroexpand-1 '(0XCB0::js-source-file "foo"))
(:SCRIPT :TYPE "text/javascript" :HREF "foo")
但是,如果我将其包含在我的 page
函数中:
(defun page (test)
(with-html-output-to-string
(*standard-output* nil :prologue nil :indent t)
(:script
(js-source-file "foo"))))
...在定义新的page
函数时,它会给我一个样式警告(undefined function: :SCRIPT
)。此外,page
函数在执行时会产生此错误:
*(0xcb0::page "foo")
The function :SCRIPT is undefined.
[Condition of type UNDEFINED-FUNCTION]
为什么嵌入式宏 js-source-file
可以按预期工作,因为它会产生所需的输出,但在另一个函数中调用时会失败?
P.S。我知道 Lisp 中的宏主题对于像我这样的初学者来说可能会让人筋疲力尽。但目前我无法理解这应该有效但无效的事实!
问题是宏的展开有点违反直觉,顺序是从最外层到最内层。例如:
(defmacro foobar (quux)
(format t "Foo: ~s~%" quux))
(defmacro do-twice (form)
`(progn
,form
,form))
(foobar (do-twice (format t "qwerty")))
输出将是
Foo: (DO-TWICE (FORMAT T "qwerty"))
foobar
从未见过 do-twice
的扩展。您可以通过在 foobar
:
中自己调用 macroexpand
来避免该问题
(defmacro foobar (quux)
(format t "Foo: ~s~%" (macroexpand quux)))
(foobar (do-twice (format t "qwerty")))
; => Foo: (PROGN (FORMAT T "qwerty") (FORMAT T "qwerty"))
由于您使用的是第三方宏,这可能不是一个好的解决方案。我认为最好的选择是在 js-source-file
中自己生成标记。我不熟悉 cl-who
,但这在我的快速测试中似乎有效:
(defun js-source-file (filename stream)
(with-html-output (stream nil :prologue nil :indent t)
(:script :type "text/javascript" :href filename))))
(defun page (test)
(with-output-to-string (str)
(with-html-output (str nil :prologue nil :indent t)
(:script
(js-source-file test str)))))
除了 cl-who 手册的 , I'll cover the particular case of with-html-output
. This is derived from the Syntax and Semantics 部分。
首先注意,如果你自己宏展开调用,你可以看到with-html-output
建立了macrolet
绑定,例如str
、htm
、ftm
, esc
... htm
macrolet 不带任何参数(主体除外)并扩展为具有相同参数的 with-html-output
形式具有词法封闭的 with-html-output
宏.
为了修复您的代码,您可以按如下方式修改您的宏:
(defmacro js-source-file (filename)
`(htm (:script :type "text/javascript" :href ,filename)))
然后:
with-html-output
扩展为包含 (js-source-file ...)
的树
- 您的宏已展开并生成
(htm (:script ...))
形式。
- 然后,宏展开以产生内部
(with-html-output ...)
形式。
- 内部
with-html-output
扩展并处理(:script ...)
。
您必须在此处选择是喜欢使用宏还是函数。函数通常不是内联的,可以很容易地在运行时重新定义。理论上宏也可以在运行时扩展,但在大多数实现(和默认配置)中,您必须重新编译任何依赖于宏的函数。你也可以让宏调用一个辅助函数。
我正在学习 Common Lisp,想玩玩 lisp 和 web 开发。我当前的问题来自一个简单的想法,即遍历我想要包含的所有 javascript 文件。我使用 SBCL 和 Quicklisp 来快速启动。问题可能与我正在使用的 cl-who
包有关。
所以我已经声明了我的包裹并开始这样:
(defpackage :0xcb0
(:use :cl :cl-who :hunchentoot :parenscript))
(in-package :0xcb0)
为了简单起见,我减少了我的问题函数。所以我有这个 page
函数:
(defun page (test)
(with-html-output-to-string
(*standard-output* nil :prologue nil :indent t)
(:script
(:script :type "text/javascript" :href test))))
这将产生所需的输出
*(0xcb0::page "foo")
<script>
<script type='text/javascript' href='foo'></script>
</script>
现在我已经创建了一个生成 :script
个标签的宏。
(defmacro js-source-file (filename)
`(:script :type "text/javascript" :href ,filename)))
这按预期工作:
*(macroexpand-1 '(0XCB0::js-source-file "foo"))
(:SCRIPT :TYPE "text/javascript" :HREF "foo")
但是,如果我将其包含在我的 page
函数中:
(defun page (test)
(with-html-output-to-string
(*standard-output* nil :prologue nil :indent t)
(:script
(js-source-file "foo"))))
...在定义新的page
函数时,它会给我一个样式警告(undefined function: :SCRIPT
)。此外,page
函数在执行时会产生此错误:
*(0xcb0::page "foo")
The function :SCRIPT is undefined.
[Condition of type UNDEFINED-FUNCTION]
为什么嵌入式宏 js-source-file
可以按预期工作,因为它会产生所需的输出,但在另一个函数中调用时会失败?
P.S。我知道 Lisp 中的宏主题对于像我这样的初学者来说可能会让人筋疲力尽。但目前我无法理解这应该有效但无效的事实!
问题是宏的展开有点违反直觉,顺序是从最外层到最内层。例如:
(defmacro foobar (quux)
(format t "Foo: ~s~%" quux))
(defmacro do-twice (form)
`(progn
,form
,form))
(foobar (do-twice (format t "qwerty")))
输出将是
Foo: (DO-TWICE (FORMAT T "qwerty"))
foobar
从未见过 do-twice
的扩展。您可以通过在 foobar
:
macroexpand
来避免该问题
(defmacro foobar (quux)
(format t "Foo: ~s~%" (macroexpand quux)))
(foobar (do-twice (format t "qwerty")))
; => Foo: (PROGN (FORMAT T "qwerty") (FORMAT T "qwerty"))
由于您使用的是第三方宏,这可能不是一个好的解决方案。我认为最好的选择是在 js-source-file
中自己生成标记。我不熟悉 cl-who
,但这在我的快速测试中似乎有效:
(defun js-source-file (filename stream)
(with-html-output (stream nil :prologue nil :indent t)
(:script :type "text/javascript" :href filename))))
(defun page (test)
(with-output-to-string (str)
(with-html-output (str nil :prologue nil :indent t)
(:script
(js-source-file test str)))))
除了 cl-who 手册的 with-html-output
. This is derived from the Syntax and Semantics 部分。
首先注意,如果你自己宏展开调用,你可以看到with-html-output
建立了macrolet
绑定,例如str
、htm
、ftm
, esc
... htm
macrolet 不带任何参数(主体除外)并扩展为具有相同参数的 with-html-output
形式具有词法封闭的 with-html-output
宏.
为了修复您的代码,您可以按如下方式修改您的宏:
(defmacro js-source-file (filename)
`(htm (:script :type "text/javascript" :href ,filename)))
然后:
with-html-output
扩展为包含(js-source-file ...)
的树
- 您的宏已展开并生成
(htm (:script ...))
形式。 - 然后,宏展开以产生内部
(with-html-output ...)
形式。 - 内部
with-html-output
扩展并处理(:script ...)
。
您必须在此处选择是喜欢使用宏还是函数。函数通常不是内联的,可以很容易地在运行时重新定义。理论上宏也可以在运行时扩展,但在大多数实现(和默认配置)中,您必须重新编译任何依赖于宏的函数。你也可以让宏调用一个辅助函数。