Sod*_*hty 2 macros sbcl common-lisp
我正在使用SBCL Common Lisp。我不是专家,但我想认为我对它的理解足够好,可以混为一谈。但是,我最近遇到了一个奇怪的问题defmacro。
为什么以下代码无法编译,以及如何对其进行更改以使其编译?
(let ((a nil))
(defmacro testmacro ())
(testmacro))
Run Code Online (Sandbox Code Playgroud)
错误是:
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))
Run Code Online (Sandbox Code Playgroud)
当然,显而易见的答案是defmacro将let绑定置于外部。但是,由于技术原因,这对我的项目来说非常不便。此外,我不知道为什么没有理由在let绑定下定义宏会失败。
Common Lisp中是否特别禁止使用“让宏”?还是我错过了什么?
编辑:我的要求的症结是宏共享与它调用的函数相同的级别,并且后续代码不嵌套在其中。
这是因为我试图编写同时生成函数的宏和宏。生成的宏将调用该函数。因此,我们最终得到如下所示的结果。
我写这样的一行:
(generate-stuff function-name)
Run Code Online (Sandbox Code Playgroud)
这就解决了:
(defun function-name-1 () ...)
(defmacro function-name (&rest args)
`(function-name-1 ,@args)
Run Code Online (Sandbox Code Playgroud)
因此,宏及其调用的函数必须处于相同的词汇级别,并且彼此相邻,并且不能macrolet为随后的代码嵌套而创建新的词汇环境()。
一切都可以正常工作,只不过我当时恰好在一个let绑定中。因为有问题的函数必须引用此绑定中的变量。
请注意,可以从绑定外部访问该宏:
(let ...)
(defmacro my-macro ...)
(my-macro)
Run Code Online (Sandbox Code Playgroud)
在我看来,let绑定内部定义的宏仅应在绑定结束后才能访问,这很荒谬。Lisp的其他动作都不会有这种表现。
可以使用MACROLET定义本地宏。
此外,我不知道为什么在let绑定下定义宏会失败的正当理由。
它不会失败。如果您只是编译文件或编译表达式,它在编译期间就不可用。defmacro如果定义不是顶级的,则编译器不会在编译时环境中提供定义。在的内部progn,它将位于顶层,但不在的内部let。
切记:SBCL使用编译器将Lisp源代码编译为机器代码。它执行机器代码。
让我们看一下您的示例:
(let ((a nil))
(defmacro testmacro ())
(testmacro))
Run Code Online (Sandbox Code Playgroud)
通常,将全局宏放入LET中是一种不好的做法,并且很难理解其实际功能。绑定的影响应该a是什么?请记住,编译器在执行之前先编译代码。
假设我们有SBCL,并且SBCL以上述形式加载文件:
(load "foo.lisp")
Run Code Online (Sandbox Code Playgroud)
SBCL现在要做:阅读整个表格,编译整个表格,执行整个表格。以该顺序。
SBCL 读取第一个表格。等效于:
CL-USER 155 > (read-from-string "(let ((a nil))
(defmacro testmacro ())
(testmacro))")
(LET ((A NIL)) (DEFMACRO TESTMACRO NIL) (TESTMACRO))
Run Code Online (Sandbox Code Playgroud)
这样我们就有了数据。
下一步是SBCL 编译该代码。
下一步是SBCL 执行已编译的代码
aTESTMACRO定义了名为的全局宏那完全是合乎逻辑的,绝不是absurd。SBCL首先读取LET,然后将LET编译为机器代码,然后运行机器代码。
这是三个独立的步骤:读取时间,编译时间,运行时。
让我们在REPL中尝试一下:
首先,我们正在阅读表格
* (read-from-string "(let ((a nil))
(defmacro testmacro ())
(testmacro))")
(LET ((A NIL))
(DEFMACRO TESTMACRO ())
(TESTMACRO))
129
Run Code Online (Sandbox Code Playgroud)
然后我们在编译表格:
* (compile nil `(lambda () ,*))
; in: LAMBDA ()
; (LET ((A NIL))
; (DEFMACRO TESTMACRO ())
; (TESTMACRO))
;
; caught STYLE-WARNING:
; The variable A is defined but never used.
; in: LAMBDA ()
; (TESTMACRO)
;
; caught STYLE-WARNING:
; undefined function: COMMON-LISP-USER::TESTMACRO
;
; compilation unit finished
; Undefined function:
; TESTMACRO
; caught 2 STYLE-WARNING conditions
#<FUNCTION (LAMBDA ()) {226B025B}>
T
NIL
Run Code Online (Sandbox Code Playgroud)
您可以看到SBCL告诉我们一些问题。TESTMACRO未定义。
现在我们运行代码:
* (funcall *)
STYLE-WARNING:
TESTMACRO is being redefined as a macro when it was previously assumed to be a function.
debugger invoked on a UNDEFINED-FUNCTION in thread
#<THREAD "main thread" RUNNING {10004F84C3}>:
The function COMMON-LISP-USER::TESTMACRO is undefined.
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [CONTINUE ] Retry calling TESTMACRO.
1: [USE-VALUE ] Call specified function.
2: [RETURN-VALUE ] Return specified values.
3: [RETURN-NOTHING] Return zero values.
4: [ABORT ] Exit debugger, returning to top level.
("undefined function")
0]
Run Code Online (Sandbox Code Playgroud)
不出所料-编译器确实警告我们:该函数TESTMACRO未定义。
如果要SBCL编译宏格式,则必须确保在编译时知道该宏格式。