如何在宏中调用其他函数/宏?以下内容似乎无效(即使我用bar
定义了define-syntax
)
(define (bar) #'"hello")
(define-syntax (foo stx)
(syntax-case stx ()
[(_ '(a b)) (bar)]))
答案 0 :(得分:4)
Racket的宏系统在运行时代码和编译时代码之间保持谨慎的分离。您将bar
定义为运行时函数,但实际上您想在宏中使用它。因此,您需要在编译时通过用bar
包装它来明确定义begin-for-syntax
:
(begin-for-syntax
(define (bar) #'"hello"))
这将解决您的问题。有关更多信息,请参见《球拍指南》中的Compile and Run-Time Phases。
为什么这是必需的?好吧,各种原因。首先,通过将运行时代码与编译时代码区分开来,编译器可以保证何时加载代码。例如,您可能在宏的实现中使用了一个库,但可能永远不会在运行时使用该库。通过注意分开运行时和编译时,编译器可以确保只在编译时而不是在运行时加载库。
在Racket中,我们将代码可以运行的不同时间称为 phases ,并为每个阶段分配一个数字。例如,运行时是阶段0 ,而编译时是阶段1 。为什么要麻烦使用数字?好吧,事实证明,不仅有两个阶段!实际上,Racket中可以有任意数量的编译阶段,可以继续进行第2阶段,第3阶段,依此类推。
因此,如果阶段1是编译时,那么阶段2是什么?好吧,如果您在另一个宏的实现中使用宏呢?如果直接尝试,将无法正常工作:
(define-syntax (foo stx)
(syntax-case stx ()
[(_) #''foo]))
(define-syntax (bar stx)
(syntax-case stx ()
[(_) (foo)]))
再一次,上述程序将抱怨foo
是未绑定的,因为foo
是在阶段0定义的,但是bar
内部的代码是在阶段1的。因此,我们需要将foo
的定义包装在begin-for-syntax
中,就像以前一样:
(begin-for-syntax
(define-syntax (foo stx)
(syntax-case stx ()
[(_) #''foo])))
但是这里的问题是:实现foo
的代码处于哪个阶段?它显然不是阶段0,因为它是一个宏,但是它也不是阶段1,因为它是在编译时定义的宏 (因为它包装在begin-for-syntax
中) 。因此,foo
的主体处于阶段2!
实际上,如果您尝试编写上述代码,则可能会收到一些错误,说明许多事情是未绑定的。当您编写#lang racket
时,将在阶段0和阶段1自动导入内容,但通常,模块也仅在各个阶段中导入。为了使以上代码片段起作用,我们需要在第2阶段导入racket/base
,如下所示:
(require (for-meta 2 racket/base))
阶段的所有详细信息不在此答案的范围内,但是我要说明的是,Racket中的阶段很重要,并且在编写宏时,您必须担心它们。有关更彻底的处理,请参见《球拍指南》中的General Phase Levels,该指南对先前链接的介绍性部分进行了补充和扩展。有关为何相位级别很重要以及没有相位分离时出了什么问题的更多详细信息,请参阅论文Composable and Compilable Macros的(可读性)第一部分,该部分首先介绍了Racket的模块系统。