Modules
模块用于帮助将程序组织成单独的部分,这些部分通过声明好的接口干净地交互。尽管模块化编程通常可以为多人参加的大型程序开发带来便利,但它也可以在 Chez Scheme 中以“微模块”级别使用,因为 Chez Scheme 中的模块和 import 形式属于定义,并且可以出现在定义可以出现的任意位置,包括在 lambda 表达式的程序体或其他局部作用域中。
微模块与函数孰好???还是视情况而定???
模块控制绑定的可见性,可以用作扩展词法作用域,以允许更精确地控制绑定的可见范围。模块导出标识符的绑定,即变量绑定、关键字绑定或模块名绑定。模块可以是具名的抑或匿名的。只要模块名可见,那么在具名模块出现处导入的绑定都可见。匿名模块中导出的绑定,在模块出现的地方被隐式地导入。匿名模块可用于隐藏一组绑定中的一些绑定,同时允许其余绑定可见。
语法
(module name interface defn … init …)
(module interface defn … init …)
name 是标识符,defn … 是定义,init … 是表达式。interface 是形如 (export …) 的导出表,其中每个 export 都是标识符 identifier 抑或形式 (identifier export …)。
模块由一组(可能为空的)定义和一组(可能为空的)初始化表达式序列组成。模块中定义的标识符(可理解为变量或者状态,下同)在模块的程序体中可见,被导出的标识符在模块的导入作用域内亦可见。模块接口中列出的每个标识符必须在该模块中定义或被导入该到模块。module 形式是一种定义,因此可以出现在其他定义可以出现的任何位置,包括嵌套在 lambda 表达式的程序体、 library 形式、顶层程序,以及嵌套在其他模块中。此外,因为模块名的作用域与其他标识符相同,所以模块和库可以像变量和关键字那样导出模块名。
模块名与其他标识符占用相同的名字空间,并遵循相同的作用域规则。除非被导出,否则模块中定义的标识符仅在该模块中可见。
模块内的表达式可以引用在模块外部绑定的标识符。
(let ([x 3])
(module m (plusx)
(define plusx (lambda (y) (+ x y))))
(import m)
(let ([x 4])
(plusx 5))) ; => 8,注意不是9哦,可以将plusx视为:(define plusx (lambda (y) (+ 3 y))))
同样,import不会阻止访问出现导入表单的可见标识符,但import标识符所覆盖的变量除外(啥叫覆盖,看下面两段代码)。
(module m (y) (define y 'm-y))
(let ([x 'local-x] [y 'local-y])
(import m)
(list x y)) ; => (local-x m-y)
模块m中定义了标识符y, 在其被import之后会覆盖let中的y;
(module m (y) (define y 'm-y))
(let ()
(import m)
(let ([x 'local-x] [y 'local-y]) (list x y)) ; => (local-x local-y)
)
先import m,之后再定义y,模块中定义的y不会覆盖let的y。
另一方面,在模块中使用 import-only 会建立一个隔离的作用域,其中唯一可见的是被导入模块所导出的标识符,下面的代码中只有y可见。 对于静态验证,有时不希望使用任何标识符,除了明确导入模块或本地范围的标识符外,这有时是合乎需要的。
(module m (y) (define y 'm-y))
(let ([x 'local-x] [y 'local-y])
(import-only m)
x) ; => Error: x is not visible
除非通过“import-only”导入的模块导出“import”或“import-only”以及至少一个模块的名称,否则无法在“import-only”范围内进行后续导入。 要创建一个包含多个模块导出的隔离范围而又不使“import”或“import-only”可见,必须以相同的“import-only”形式列出所有要导入的模块。
另一种解决方案是创建一个包含每个其他模块的导出的模块。
(module m2 (y) (define y 'y))
(module m1 (x) (define x 'x))
(module mega-module (cons x y)
(import m1)
(import m2)
(import scheme))
(let ([y 3])
(import-only mega-module)
(cons x y)) ; => (x . y)
在它被编译之前,源程序被翻译成不含语法抽象、语法定义、库定义、模块定义以及 import 形式的核心语言程序。翻译由语法展开器负责,语法展开器以递归下降的方式处理源程序中的形式。
module 和 import 特殊形式仅影响标识符(identifier)在源程序中的可见性,而不影响其含义。特别地,无论变量被绑定到在模块内部或外部定义的位置,import 都不会引入新位置。为了保持由模块和语法抽象建立的作用域关系,局部变量在必要时会被重命名。因此,表达式:
(let ([x 1])
(module m (x setter)
(define-syntax x (identifier-syntax z))
(define setter (lambda (x) (set! z x)))
(define z 5))
(let ([y x] [z 0])
(import m)
(setter 3)
(+ x y z))) ; => 4
等价于下面的表达式,其中标识符被统一地更名,并带上了下标:
(let ([x0 1])
(define-syntax x1 (identifier-syntax z1))
(define setter1 (lambda (x2) (set! z1 x2)))
(define z1 5)
(let ([y3 x0] [z3 0])
(setter1 3)
(+ x1 y3 z3)))
互递归module
(module (a b)
(module a (x) (define x (lambda () y)))
(module b (y) (define y (lambda () x)))
(import a)
(import b))
此模式的一般化语法定义如下,允许定义多个互递归的模块
(define-syntax rec-modules
(syntax-rules (module)
[(_ (module m (id ...) form ...) ...)
(module (m ...)
(module m (id ...) form ...) ...
(import m) ...)]))
组合module
由于模块可以重新导出所导入的绑定,所以很容易在单个模块上提供多个视图,就像下面的 s 和 t 为 r 提供的视图那样,或者将几个模块组合成一个复合,就像 r 那样。
(module p (x y)
(define x 1) (define y 2))
(module q (y z)
(define y 3) (define z 4))
(module r (a b c d)
(import* p (a x) (b y))
(import* q (c y) (d z)))
(module s (a c) (import r))
(module t (b d) (import r))
import*
稍微解释一下import*, 其语法定义如下:
(define-syntax import*
(syntax-rules ()
[(_ m) (begin)]
[(_ m (new old))
(module (new)
(module (tmp)
(import m)
(alias tmp old))
(alias new tmp))]
[(_ m id) (module (id) (import m))]
[(_ m spec0 spec1 ...)
(begin (import* m spec0) (import* m spec1 ...))]))
它支持重命名import的绑定和选择性导入特定的绑定,无需使用内置的import子形式来选择和重命名标识符,举例来说:
(let ()
(import* scheme (+ cons) (cons +)) (+ (cons 1 2) (cons 3 4))) ⇒ (3 . 7)
(let ()
(import* (rnrs) (+ cons) (cons +)) (+ (cons 1 2) (cons 3 4))) ⇒ (3 . 7)
元定义Meta Definitions
语法:(meta . definition)
meta 关键字实际上是一个可以放在任何定义关键字前面的前缀,例如,
(meta define x 3)
它告诉展开器,该定义式产生的任何变量定义都只是展开期定义,只能用于其他元定义的右侧,最主要是用在转换器表达式中。它用于定义展开期辅助函数,以及供一个或多个 syntax-case 转换器所使用的其他信息。
元定义的语义类似于let*,它可以是自递归,但不是互递归的。
元定义通过宏展开传播,因此可以编写,例如:
(module (a)
(meta define-record foo (x))
(define-syntax a
(let ([q (make-foo #''q)])
(lambda (x) (foo-x q)))))
a ;=> q
条件展开Conditional expansion
可以通过 meta-cond 在展开期做出决策,这类似于 cond,但是是在展开期求值并测试表达式,并且可以在预期为定义的上下文中以及在表达式上下文中使用。
语法:(meta-cond clause1 clause2 …)
除了最后一个 clause,其余的必须采取以下形式:
(test expr1 expr2 …)
最后一个 cluase 除可采取上述形式外,还可以使用下面的 else 子句形式:
(else expr1 expr2 …)
在展开期间,test 表达式会被顺序地求值,直到某条表达式求值为真或者求值完所有的表达式为止。如果一条 test 表达式求值为真,则 meta-cond 形式将展开为包含相应表达式 expr1 expr2 … 所组成的 begin 形式。如果没有求值为真的 test 表达式,且存在 else 子句,则 meta-cond 形式将展开为由来自于 else 子句的表达式 expr1 expr2 … 所组成的 begin 形式。否则,meta-cond 表达式展开为对 void 过程的调用。
meta-cond 可以按如下定义:
(define-syntax meta-cond
(syntax-rules ()
[(_ [a0 a1 a2 ...] [b0 b1 b2 ...] ...)
(let-syntax ([expr (cond
[a0 (identifier-syntax (begin a1 a2 ...))]
[b0 (identifier-syntax (begin b1 b2 ...))]
...)])
expr)]))
meta-cond 用于在展开期从一组可能的形式中进行选择。例如,程序员可以定义过程的安全(进行错误检查)和不安全(不进行错误检查)版本,并根据编译期优化级别决定调用版本,如下所示:
(meta-cond
[(= (optimize-level) 3) (unsafe-frob x)]
[else (safe-frob x)])
别名Aliases
语法:(alias id1 id2)
alias 是一类定义,可以出现在其他定义可以出现的任何位置。它用于将绑定从一个标识符转移到另一个标识符。
(let ([x 3]) (alias y x) (set! y 4) (list x y)) ; => (4 4)
由于展开是由左至右进行的,别名应该出现在右侧的标识符的定义之后,例如:
(let ()
(import-only (chezscheme))
(define y 3)
(alias x y)
x) ; => 3
而不是:
(let ()
(import-only (chezscheme))
(alias x y)
(define y 3)
x) ; => exception: unbound identifier