首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在sbcl通用lisp中定义let绑定中的宏?

如何在sbcl通用lisp中定义let绑定中的宏?
EN

Stack Overflow用户
提问于 2019-08-23 02:29:32
回答 1查看 684关注 0票数 2

我正在使用SBCL通用Lisp。我不是专家,但我想我能理解得很好,能勉强过日子。然而,我最近遇到了一个关于defmacro的奇怪问题。

为什么以下代码不编译,以及如何将其更改为编译?

代码语言:javascript
复制
(let ((a nil))
  (defmacro testmacro ())
  (testmacro))

错误是:

代码语言:javascript
复制
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绑定下定义宏应该失败。

在通用Lisp中是否明确禁止“让宏过关”?还是我漏掉了什么?

编辑:--我需求的关键是宏与其调用的函数共享相同的级别,并且后续代码不嵌套在其中。

这是因为我试图编写一个同时生成函数和宏的宏。生成的宏将调用该函数。因此,我们最终得到的结果如下。

我写了一句话:

代码语言:javascript
复制
(generate-stuff function-name)

这将决定:

代码语言:javascript
复制
(defun function-name-1 () ...)
(defmacro function-name (&rest args)
   `(function-name-1 ,@args)

因此,它调用的宏和函数必须位于相同的词法级别,相邻,不能为后续代码嵌套创建新的词法环境(macrolet)。

这一切都会正常工作,只是我当时恰好在let绑定中;因为所讨论的函数必须引用此绑定中的变量。

请注意,可以从绑定外部访问宏:

代码语言:javascript
复制
(let ...)
   (defmacro my-macro ...)

(my-macro)

在我看来,只有在绑定结束后才能访问在let绑定中定义的宏,这似乎是荒谬的。在lisp中没有任何其他的行为是这样的。

EN

回答 1

Stack Overflow用户

发布于 2019-08-23 10:40:24

可以使用马科勒特定义本地宏。

此外,我不知道为什么在let绑定下定义宏应该失败。

不会失败的。如果您只是编译文件或编译表达式,则在编译过程中它是不可用的。如果定义不在顶层,编译器不会使defmacro定义在编译时环境中可用。在progn内部,它将位于顶层,而不是在let内部。

记住: SBCL使用编译器将Lisp源代码编译成机器代码。它执行机器代码.

让我们看看您的例子:

代码语言:javascript
复制
(let ((a nil))
  (defmacro testmacro ())
  (testmacro))

通常,将全局宏放入LET是一种糟糕的做法,很难理解它实际上应该做什么。a结合的影响应该是什么?记住,编译器在执行之前编译代码

让我们假设我们有SBCL和SBCL用上面的表单加载一个文件:

代码语言:javascript
复制
(load "foo.lisp")

SBCL现在做的是:读取整个表单,编译整个表单,执行整个表单。按这个顺序.

SBCL 读取的第一种形式。相当于:

代码语言:javascript
复制
CL-USER 155 > (read-from-string "(let ((a nil))
                                   (defmacro testmacro ())
                                   (testmacro))")
(LET ((A NIL)) (DEFMACRO TESTMACRO NIL) (TESTMACRO))

因此,我们有数据。

下一步是SBCL 编译该代码。

  • 它看到了一个LET表达式。它为LET绑定创建代码。它不执行LET。我们只是在编译它。
  • 然后编译LET的主体。
  • 它看到一个DEFMACRO表单:宏被展开,并被编译成机器代码。它不执行DEFMACRO。我们只是在扩充和汇编它。
  • 它看到了TESTMACRO的形式:它不知道它是什么。它将其编译为对未定义函数的函数调用,并对未定义函数发出警告。

下一步是SBCL 执行编译的代码。

  • 它运行已编译的LET表单,并为a创建绑定。
  • 然后,它执行LET的主体。
  • 它执行已编译的DEFMACRO表单:定义一个名为TESTMACRO的全局宏。
  • 它执行已编译的TESTMACRO表单:函数被调用。它没有被定义。一个错误被发出信号。

这是合乎逻辑的,绝不是absurd。SBCL首先读取LET,然后将LET编译为机器代码,然后运行机器代码。

以下是三个独立的步骤:读取时、编译时、运行时.

,让我们在REPL:中尝试一下

首先,我们是READing,形式

代码语言:javascript
复制
* (read-from-string "(let ((a nil))
                       (defmacro testmacro ())
                       (testmacro))")
(LET ((A NIL))
  (DEFMACRO TESTMACRO ())
  (TESTMACRO))
129

然后,我们正在编写以下表单:

代码语言:javascript
复制
* (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

你可以看到SBCL告诉我们一些问题。TESTMACRO是未定义的。

现在我们运行代码:

代码语言:javascript
复制
* (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] 

正如预期的那样-编译器确实警告我们:函数TESTMACRO是未定义的。

如果希望SBCL编译宏表单,则必须确保在编译时该宏表单是已知的。

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57619150

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档