如何在宏中调用其他函数/宏?以下似乎不起作用(即使我用 define-syntax 定义 bar )
define-syntax
bar
(define (bar) #'"hello") (define-syntax (foo stx) (syntax-case stx () [(_ '(a b)) (bar)]))
Racket的宏系统在运行时和编译时代码之间保持谨慎的分离 . 您将 bar 定义为运行时函数,但实际上您希望在宏中使用它 . 因此,您需要在编译时通过使用 begin-for-syntax 包装它来显式定义 bar :
begin-for-syntax
(begin-for-syntax (define (bar) #'"hello"))
这将解决您的问题 . 有关更多信息,请参阅“球拍指南”中的Compile and Run-Time Phases .
为什么这有必要?各种原因 . 首先,通过明确区分运行时代码和编译时代码,编译器可以对何时加载代码做出某些保证 . 例如,您可能在宏的实现中使用库,但您可能永远不会在运行时使用该库 . 通过注意分离运行时和编译时,编译器可以确保只在编译时加载库,而不是在运行时 .
在Racket中,我们称代码可以运行阶段的不同时间,并且我们为每个阶段分配一个数字 . 例如,运行时为阶段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.因此,我们需要在 begin-for-syntax 中包装 foo 的定义,就像之前一样:
foo
(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 ,如下所示:
#lang racket
racket/base
(require (for-meta 2 racket/base))
阶段的所有细节都超出了本答案的范围,但我要说的是,Racket中的阶段很重要,当你编写宏时,你必须担心它们 . 有关更彻底的处理,请参阅“球拍指南”中的General Phase Levels,它补充并扩展了之前链接的介绍部分 . 有关为什么相位水平很重要以及在没有相分离时出现问题的详细信息,请参阅文章Composable and Compilable Macros的第一部分(非常易读),该部分首先介绍了Racket的模块系统 .
1 回答
Racket的宏系统在运行时和编译时代码之间保持谨慎的分离 . 您将
bar
定义为运行时函数,但实际上您希望在宏中使用它 . 因此,您需要在编译时通过使用begin-for-syntax
包装它来显式定义bar
:这将解决您的问题 . 有关更多信息,请参阅“球拍指南”中的Compile and Run-Time Phases .
为什么这有必要?各种原因 . 首先,通过明确区分运行时代码和编译时代码,编译器可以对何时加载代码做出某些保证 . 例如,您可能在宏的实现中使用库,但您可能永远不会在运行时使用该库 . 通过注意分离运行时和编译时,编译器可以确保只在编译时加载库,而不是在运行时 .
在Racket中,我们称代码可以运行阶段的不同时间,并且我们为每个阶段分配一个数字 . 例如,运行时为阶段0,编译时间为阶段1.为什么要使用数字呢?嗯,事实证明,不仅仅是两个阶段!事实上,在Racket中可以有任意数量的编译阶段,继续阶段2,阶段3,等等 .
因此,如果阶段1是编译时,第2阶段是什么?那么,如果你在另一个宏的实现中使用宏呢?如果您直接尝试,它将无法正常工作:
再一次,上面的程序会抱怨
foo
是未绑定的,因为foo
在阶段0定义,但bar
内的代码处于阶段1.因此,我们需要在begin-for-syntax
中包装foo
的定义,就像之前一样:但问题是:实现
foo
的代码是什么阶段?它显然不是阶段0,因为它是一个宏,但它也不是阶段1,因为它是在编译时定义的宏(因为它包含在begin-for-syntax
中) . 因此,foo
的正文处于第2阶段!实际上,如果您尝试编写上面的代码,您可能会遇到一些错误,很多东西都没有绑定 . 当您编写
#lang racket
时,会在阶段0和阶段1自动导入事物,但通常,模块也仅在各个阶段导入 . 为了使上面的代码段工作,我们需要在阶段2导入racket/base
,如下所示:阶段的所有细节都超出了本答案的范围,但我要说的是,Racket中的阶段很重要,当你编写宏时,你必须担心它们 . 有关更彻底的处理,请参阅“球拍指南”中的General Phase Levels,它补充并扩展了之前链接的介绍部分 . 有关为什么相位水平很重要以及在没有相分离时出现问题的详细信息,请参阅文章Composable and Compilable Macros的第一部分(非常易读),该部分首先介绍了Racket的模块系统 .