【发布时间】:2019-12-28 08:34:33
【问题描述】:
我正在使用SBCL Common Lisp。我不是专家,但我喜欢认为我理解它足以应付。不过最近遇到defmacro一个奇怪的问题。
为什么下面的代码无法编译,如何修改才能编译?
(let ((a nil))
(defmacro testmacro ())
(testmacro))
错误是:
Unhandled UNDEFINED-FUNCTION in thread #<SB-THREAD:THREAD "main thread" RUNNING
{100399C9A3}>:
The function COMMON-LISP-USER::TESTMACRO is undefined.
Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {100399C9A3}>
0: ((LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX))
1: (SB-IMPL::CALL-WITH-SANE-IO-SYNTAX #<CLOSURE (LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX) {1003A0EA8B}>)
2: (SB-IMPL::%WITH-STANDARD-IO-SYNTAX #<CLOSURE (LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX) {1003A0EA5B}>)
3: (PRINT-BACKTRACE :STREAM #<SB-SYS:FD-STREAM for "standard error" {10039A22B3}> :START 0 :FROM :INTERRUPTED-FRAME :COUNT NIL :PRINT-THREAD T :PRINT-FRAME-SOURCE NIL :METHOD-FRAME-STYLE NIL)
4: (SB-DEBUG::DEBUGGER-DISABLED-HOOK #<UNDEFINED-FUNCTION TESTMACRO {1003A0C193}> #<unavailable argument>)
5: (SB-DEBUG::RUN-HOOK *INVOKE-DEBUGGER-HOOK* #<UNDEFINED-FUNCTION TESTMACRO {1003A0C193}>)
6: (INVOKE-DEBUGGER #<UNDEFINED-FUNCTION TESTMACRO {1003A0C193}>)
7: (ERROR UNDEFINED-FUNCTION :NAME TESTMACRO)
8: ((LAMBDA (&REST SB-C::ARGS) :IN SB-C::INSTALL-GUARD-FUNCTION))
9: (SB-INT:SIMPLE-EVAL-IN-LEXENV (LET ((A NIL)) (DEFMACRO TESTMACRO NIL) (TESTMACRO)) #<NULL-LEXENV>)
10: (EVAL-TLF (LET ((A NIL)) (DEFMACRO TESTMACRO NIL) (TESTMACRO)) 0 NIL)
11: ((LABELS SB-FASL::EVAL-FORM :IN SB-INT:LOAD-AS-SOURCE) (LET ((A NIL)) (DEFMACRO TESTMACRO NIL) (TESTMACRO)) 0)
12: ((LAMBDA (SB-KERNEL:FORM &KEY :CURRENT-INDEX &ALLOW-OTHER-KEYS) :IN SB-INT:LOAD-AS-SOURCE) (LET ((A NIL)) (DEFMACRO TESTMACRO NIL) (TESTMACRO)) :CURRENT-INDEX 0)
13: (SB-C::%DO-FORMS-FROM-INFO #<CLOSURE (LAMBDA (SB-KERNEL:FORM &KEY :CURRENT-INDEX &ALLOW-OTHER-KEYS) :IN SB-INT:LOAD-AS-SOURCE) {10039B19FB}> #<SB-C::SOURCE-INFO {10039B19B3}> SB-C::INPUT-ERROR-IN-LOAD)
14: (SB-INT:LOAD-AS-SOURCE #<SB-SYS:FD-STREAM for "file /mnt/nas-data/Documents/Projects/SHARED PROJECTS - EVAN/pkcmd-lx/temp.lisp" {10039A5103}> :VERBOSE NIL :PRINT NIL :CONTEXT "loading")
15: ((FLET SB-FASL::LOAD-STREAM :IN LOAD) #<SB-SYS:FD-STREAM for "file /mnt/nas-data/Documents/Projects/SHARED PROJECTS - EVAN/pkcmd-lx/temp.lisp" {10039A5103}> NIL)
16: (LOAD #<SB-SYS:FD-STREAM for "file /mnt/nas-data/Documents/Projects/SHARED PROJECTS - EVAN/pkcmd-lx/temp.lisp" {10039A5103}> :VERBOSE NIL :PRINT NIL :IF-DOES-NOT-EXIST T :EXTERNAL-FORMAT :DEFAULT)
17: ((FLET SB-IMPL::LOAD-SCRIPT :IN SB-IMPL::PROCESS-SCRIPT) #<SB-SYS:FD-STREAM for "file /mnt/nas-data/Documents/Projects/SHARED PROJECTS - EVAN/pkcmd-lx/temp.lisp" {10039A5103}>)
18: ((FLET #:WITHOUT-INTERRUPTS-BODY-146 :IN SB-IMPL::PROCESS-SCRIPT))
19: (SB-IMPL::PROCESS-SCRIPT "temp.lisp")
20: (SB-IMPL::TOPLEVEL-INIT)
21: ((FLET #:WITHOUT-INTERRUPTS-BODY-82 :IN SAVE-LISP-AND-DIE))
22: ((LABELS SB-IMPL::RESTART-LISP :IN SAVE-LISP-AND-DIE))
当然,显而易见的答案是将defmacro 放在let 绑定之外。但是,由于技术原因,这对我的项目来说非常不方便。此外,我知道没有正当理由为什么在 let 绑定下定义宏会失败。
Common Lisp 中是否明确禁止“放过宏”?还是我错过了什么?
编辑:我要求的关键是宏与其调用的函数共享同一级别,并且后续代码不嵌套在其中。
这是因为我正在尝试编写一个同时生成函数和宏的宏。生成的宏将调用该函数。所以我们最终得到了如下的结果。
我写了这样一行:
(generate-stuff function-name)
这会变成:
(defun function-name-1 () ...)
(defmacro function-name (&rest args)
`(function-name-1 ,@args)
因此,宏和它调用的函数必须处于同一词法级别,彼此相邻,并且不能为后续代码嵌套创建新的词法环境(macrolet)。
这一切都可以正常工作,只是我当时恰好在 let 绑定中;因为有问题的函数必须引用此绑定中的变量。
请注意,宏可以从绑定外部访问:
(let ...)
(defmacro my-macro ...)
(my-macro)
在let 绑定中定义的宏只能在绑定结束后才能访问,这对我来说似乎很荒谬。 lisp 中没有其他任何东西会以这种方式运行。
【问题讨论】:
-
规范只要求在编译时存储顶级宏。如果宏没有在同一个文件中使用并且文件在使用宏的任何文件之前加载(不仅编译),则该示例将起作用(使用解释器也可能起作用,但这取决于实现)。无论如何,您的示例无法真正实现任何有用的东西;宏将在编译时展开,而
LET绑定仅在运行时存在。 -
改用macrolet
-
我的例子只是一个例子。它不打算做任何有用的事情。据我了解,lisp 没有“编译时”和“运行时”的概念。宏只是一个函数,它的参数不被计算,其结果在返回后被计算。
macrolet在这里不是一个可行的替代方案,因为它会创建一个新的词法绑定,并且所有后续代码都需要嵌套在其中。 我要求的关键是宏和它调用的函数共享同一个层级,后面的代码不嵌套在里面。 -
“据我所知,lisp 没有“编译时”和“运行时”的概念”——您的理解是错误的。在开发过程中,当您逐段阅读、编译、宏扩展、加载和执行程序时,不同的时间经常交织在一起,但是如果您要编写任何复杂的宏,则必须了解它们之间的区别。
-
很公平。正如我所说,我不是专家。所以....做我想做的事是完全不可能的吗?
标签: macros common-lisp sbcl