【问题标题】:Undefined function after macroexpansion宏展开后未定义的函数
【发布时间】:2016-01-30 13:57:15
【问题描述】:

我正在学习 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 会按预期工作,因为它会产生所需的输出,但在另一个函数中调用时却失败了?

附:我知道对于像我这样的初学者来说,Lisp 中的宏主题可能会让人筋疲力尽。但目前我无法理解这应该有效但无效的事实!

【问题讨论】:

  • 稍后我将不得不对其进行测试,但问题可能在于with-html-output-to-string 是一个宏,它将其中的(:script ...) 表单转换为其他代码。由于js-source-file还没有展开,所以with-html-output-to-string不处理。

标签: macros common-lisp cl-who


【解决方案1】:

问题是宏按照从最外到内的顺序被直观地扩展了一点。例如:

(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)))))

【讨论】:

  • 确实似乎与with-output-to-string的内部有关。您建议的解决方案工作正常。然而,我希望在每个函数中绕过 with-html-output 辅助函数。您也对,第三方宏不是一个好的解决方案。对于测试,它似乎是正确的选择,但正如我所见,它可以单独使用 cl 完成,几乎不需要额外的工作!谢谢,它让我对 cl 的工作原理有了更深入的了解。
【解决方案2】:

除了other good answer,我还将介绍with-html-output 的特殊情况。这源自 cl-who 手册的 Syntax and Semantics 部分。

首先,注意如果你自己宏展开调用,你可以看到with-html-output建立了macrolet绑定,比如strhtmftmesc...htm macrolet 不接受任何参数(主体除外)并扩展为具有相同参数的with-html-output 形式,具有词法封闭的with-html-output 宏。 为了修复您的代码,您可以按如下方式修改您的宏:

(defmacro js-source-file (filename)
  `(htm (:script :type "text/javascript" :href ,filename)))

然后:

  1. with-html-output 扩展为包含 (js-source-file ...) 的树
  2. 您的宏已展开并生成(htm (:script ...)) 表单。
  3. 然后,宏程序被扩展以产生一个内部(with-html-output ...) 形式。
  4. 内部with-html-output被扩展并处理(:script ...)

您必须选择是否喜欢在此处使用宏或函数。函数通常不是内联的,并且可以在运行时轻松地重新定义。宏理论上也可以在运行时扩展,但在大多数实现(和默认配置)中,您必须重新编译依赖于宏的任何函数。也可以让宏调用辅助函数。

【讨论】:

  • 感谢详细的解释!对我来说,我现在将坚持使用 cl-who 并使用更多功能。这应该让我的开始更容易 :) 我之前尝试理解 cl-who 文档,但似乎我需要更多练习 cl 和随附的文档。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-26
  • 2014-07-16
  • 1970-01-01
  • 2011-08-18
  • 1970-01-01
相关资源
最近更新 更多