声明一个模块只是这个模块在当前命名空间的某个地方。这可以通过 require 来完成,也可以直接在代码中写出模块。
模块访问和实例化有点难以理解,最好这样描述:
模块访问正在运行阶段级别 1(宏/编译时间代码),而不是运行阶段级别 0(运行时间代码)。
模块实例化正在运行阶段级别 0 代码,但不是阶段级别 1 代码。
现在,棘手的一点是,要运行阶段级别 0 代码,如果尚未编译,您必须事先运行所有更高级别的阶段代码。但是,如果该模块已经被访问(并编译)过,它将不会再次运行阶段级别 1 代码。
这可以通过以下模块看到,称为test.rkt:
#lang racket
(require (for-meta 2 racket/base))
(displayln "phase 0")
(begin-for-syntax
(displayln "phase 1")
(begin-for-syntax
(displayln "phase 2")))
该模块的代码在阶段 0、阶段 1 和阶段 2(宏扩展阶段的宏扩展)运行。在这些阶段的每一个中,它都会打印一行以指示该阶段正在运行。使用这个模块,我们可以看到模块何时被实例化。
现在,让我们创建以下实例化 test.rkt 的文件:
#lang racket
(dynamic-require "test.rkt" #f)
如果我们在 DrRacket 中运行它,输出将类似于:
phase 2
phase 1
phase 0
现在,我们看到phase 0,因为模块正在被实例化。然而,在这种情况下,我们还会看到phase 2 和phase 1,因为必须实例化模块的语法阶段才能运行phase 0 代码。
但是,如果您再次运行它(假设您已打开已编译文件的缓存),您将得到:
phase 0
在这种情况下,您只会看到 phase 0,因为 phase 1 和更高版本的代码已经展开。我们也可以将0 提供给动态需求以获得类似的结果。
现在,如果不是将0 或#f 传递给dynamic-require,而是传递(void),给我们以下文件:
#lang racket
(dynamic-require "test.rkt" (void))
然后输出将是(同样,假设您已打开编译缓存),将如下所示:
phase 1
这是因为这里运行的是宏观级别(阶段 1)代码,而不是运行时(阶段 0)代码。虽然如果我们对test.rkt 稍作改动并再次保存(使缓存失效),我们会得到:
phase 2
phase 1
这是因为 Racket 必须扩展 phase 2 代码才能运行 phase 1 代码。现在编译的模块缓存已经更新了,再运行一次,只会输出:
phase 1
最后,没有(据我所知,如果我错了请更新这个),任何直接使用dynamic-require来保证高于阶段1的代码都会运行的方式。