算法语言Scheme修订6报告 R6RS简体中文翻译

7. 库

库是一个程序可以被独立发布的部分。库系统支持库内的宏定义,宏导出,且区别需要定义和导出的不同阶段。本章为库定义了符号,且为库的扩展(expansion )和执行定义了语义。

7.1. 库形式

一个库定义必须有下面的形式:

(library <library name>
  (export <export spec> ...)
  (import <import spec> ...)
  <library body>)

一个库的声明包含下面的要素:

  • <library name>指定了库的名字(可能还有版本)。
  • export子形式指定一个导出的列表,这个列表命名了定义在或导入到这个库的绑定的子集。
  • import子形式指定了作为一个导入依赖列表导入的绑定,其中每一个依赖指定:
    • 导入的库的名字,且可以可选地包含它的版本,
    • 相应的级别,比如,扩展(expand)或运行时(run time)(见7.2小节)和
    • 为了在导入库中可用的库导出的子集,和为在导入库中可用的为每一个库导出准备的名字。
  • <library body>是库的内部,由一系列定义和紧随其后的表达式组成。标识符可以既是本地的(未导出的)也是导出绑定的,且表达式是为它们的效果求值的初始化表达式。

一个标识符可以从两个或更多的库中使用相同的本地名字导入,或者使用两个不同的级别从相同的库中导入,只要每个库导出的绑定是一样的(也就是说,绑定在一个库中被定义,它只能通过导出和再导出经过导入)。否则,没有标识符可以被导入多次,被定义多次,或者同时被定义和导入。除了被显式地导入到库中或在库中定义的标识符,其它标识符在库中都是不可见的。

一个<library name>在一个实现中唯一地标识一个库,且在实现中的其它所有的库的import子句(clauses)(见下面)中是全局可见的。一个<library name>有下面的形式:

(<identifier1> <identifier2> ... <version>)

其中<version>或者是空的,或者有以下的形式:

(<sub-version> ...)

每一个<sub-version>必须表示一个精确的非负整数对象。一个空的<version>等价于()

一个<export spec>命名一个集合,这个集合包含导入的和本地的定义,这个集合将被导出,可能还会使用不同的外部名字。一个<export spec>必须是如下的形式当中的一个:

<identifier>
(rename (<identifier\(_1\)> <identifier\(_2\)>) ...)

在一个<export spec>中,一个<identifier>命名一个被定义在或被导入到库中的单独的绑定,其中,这个导入的外部名字和库中绑定的名字是一样的。一个rename指明在每一个\(\texttt{(<identifier$_1$> <identifier$_2$>)}\)这样的配对中,被命名为\(\texttt{<identifier$_1$>}\)的绑定使用\(\texttt{<identifier$_2$>}\)作为外部名字。

每一个<import spec>指定一个被导入到库中的绑定的集合,在这个集合中,级别是可见的,且通过它可以知道本地的名字。一个<import spec>必须是下面的一个:

<import set>
(for <import set> <import level> ...)

一个<import level>是下面中的一个:

run
expand
(meta <level>)

其中<level>表示一个精确的整数对象。

在一个<import level>中,run(meta 0)的缩写,且expand(meta 1)的缩写。级别和阶段(phases)在7.2小节讨论。

一个<import set>命名一个来自另一个库的绑定的集合,且可能为导入的绑定指定一个本地的名字。它必须是下面的一个:

<library reference>
(library <library reference>)
(only <import set> <identifier> ...)
(except <import set> <identifier> ...)
(prefix <import set> <identifier>)
(rename <import set> (<identifier\(_1\)> <identifier\(_2\)>) ...)

一个<library reference>通过它的名字和可选的版本标识一个库。它有下面形式中的一个:

(<identifier\(_1\)> <identifier\(_2\)> ...)
(<identifier\(_1\)> <identifier\(_2\)> ... <version reference>)

一个第一个<identifier>for, library, only, except, prefix,或rename<library reference>只允许出现在一个library <import set>中。否则<import set> (library <library reference>)等价于<library reference>

一个没有<version reference>(上面的第一个形式)的<library reference>等价于<version reference>()<library reference>

一个<version reference>指定它匹配的<version>的一个子集。<library reference>指定所有相同名字且版本匹配<version reference>的库。一个<version reference>有下面的形式:

(<sub-version reference\(_1\)> ... <sub-version reference\(_n\)>)
(and <version reference> ...)
(or <version reference> ...)
(not <version reference>)

第一个形式的一个<version reference>匹配<version>至少n个要素的,且<sub-version reference>匹配对应的<sub-version>。一个and <version reference>匹配一个版本,如果所有的跟在and后的<version references>都匹配的话。相应地,or <version reference>匹配一个版本,如果跟在or后的一个<version references>匹配的话。一个not <version reference>匹配一个版本,如果跟在其后的<version reference>都不匹配的话。

一个<sub-version reference>有下列形式之一:

<sub-version>
(>= <sub-version>)
(<= <sub-version>)
(and <sub-version reference> ...)
(or <sub-version reference> ...)
(not <sub-version reference>)

第一个形式的一个<sub-version reference>匹配一个版本,如果二者相等。第一个形式的>= <sub-version reference>匹配一个子版本如果它大于等于跟在它后面的<sub-version><=与其类似。一个and <sub-version reference>匹配一个子版本,如果所有随后的<sub-version reference>都匹配的话。相应地,一个or <sub-version reference>匹配一个子版本,如果随后的<sub-version reference>中的一个匹配的话。一个not <sub-version reference>匹配一个子版本,如果随后的<sub-version reference>都不匹配的话。

例子:

版本参考(version reference) 版本(version) 匹配?(match?)
() (1) yes
(1) (1) yes
(1) (2) no
(2 3) (2) no
(2 3) (2 3) yes
(2 3) (2 3 5) yes
(or (1 (>= 1)) (2)) (2) yes
(or (1 (>= 1)) (2)) (1 1) yes
(or (1 (>= 1)) (2)) (1 0) no
((or 1 2 3)) (1) yes
((or 1 2 3)) (2) yes
((or 1 2 3)) (3) yes
((or 1 2 3)) (4) no

当多于一个库被库参考引用的时候,库的选择有实现定义的方法决定。

为了避免诸如不兼容的类型和重复的状态这些问题,实现必须禁止库名字由相同标识符序列组成但版本不匹配的两个库在同一个程序中共存。

默认情况下,一个被导入的库导出的所有的绑定在一个使用被导入的库给的绑定的名字的导入库中是可见的。被导入的绑定和那些绑定的名字的精确的集合可以通过下面描述的only, except, prefix, 和rename形式进行调整。

  • 一个only形式产生来自另一个<import set>的绑定的一个子集,只包括被列出的<identifier>。被包含的<identifier>必须在原始的<import set>中。
  • 一个except形式产生来自另一个<import set>的绑定的一个子集,包括除了被列出的所有的<identifier>。所有被排除的<identifier>必须在原始的<import set>中。
  • 一个prefix从另一个<import set>中添加一个<identifier>前缀到每个名字中。
  • 一个rename形式,\(\texttt{(rename (<identifier$_1$> <identifier$_2$>) ...)}\),从一个中间的<import set>删除\(\texttt{<identifier$_1$> ...}\)的绑定,然后将对应的\(\texttt{<identifier$_2$> ...})添加回到最终的<import set>中。每一个\(\texttt{<identifier$_1$>}\)必须在原始的<import set>中,每一个\(\texttt{<identifier$_2$>})必须不在中间的<import set>中,且\(\texttt{<identifier$_2$>})必须是不一样的。

不符合上面的约束是一个语法错误。

一个库形式的<library body>由被分类为定义表达式的形式组成。哪些形式属于哪些类型是根据被导入的库和表达式的结果来决定的—见第10章。通常,不是定义(见11.2小节,有基本库可见的定义)的形式是表达式。

一个<library body><body>差不多(见11.3小节),除了一个<library body>不需要包括任何表达式。它必须有下面的形式:

<definition> ... <expression> ...

begin, let-syntax, 或letrec-syntax形式先于第一个表达式出现在顶层内部的时候,它们被拼接成内部;见11.4.7小节。内部的一些或所以,包括在begin, let-syntax, 或letrec-syntax形式里面的部分,可以通过一个句法抽象指定(见9.2小节)。

转换表达式和绑定像第10章描述的,按从左向右的顺序被求值和被创建。变量的表达式按从左向右求值,就像在一个隐式的letrec*中,且内部的表达式也在变量定义的表达式之后从左向右进行求值。每一个导出的变量和它本地副本值得初始化都会创建一个新的位置。两次返回给内部最后表达式的继续的行为是未定义的。

注意:出现在库语法的名字library, export, import, for, run, expand, meta, import, export, only, except, prefix, rename, and, or, not, >=, 和<=是语法的一部分,且不是被保留的,也就是说,相同的名字可以在库中用作其它的用途,甚至以不同的含义导出或导入到其它库中,这些都不会影响它们在库形式中的使用。

在一个库的里面定义的绑定在库外面的代码中是不可见的,除非绑定被显式地从这个库中导出。可是,一个导出的宏可能隐式地导出一个另外的标识符,其是在库中定义的或导入到库中的。也就是说,它可以插入标识符的一个引用到其产生的代码中。

所有的显式地导出的变量在导出的和导入的的库中都是不可变的。因此,不管是在导入的还是导出的库中,一个显式地导出的变量出现在一个set!表达式的左边是一个语法错误。

所有的隐式地导出的变量在导出的和导入的的库中也都是不可变的。因此,在一个变量被定义的库的外面通过宏产生的代码中,如果这个变量出现在一个set!表达式的左边,那么这是一个语法错误。如果一个被赋值的变量的引用出现在这个变量被定义的库的外面通过宏产生的代码中,那么这也是一个语法错误,其中,一个被赋值的变量指出现在导出库中一个set!表达式左边的变量。

所有其它的定义在一个库中的变量是可变的。

7.2. 导入和导出级别

扩展一个库可能需要来自其它库的运行时信息。比如,如果一个宏转换调用一个来自库A的过程,那么在库B中扩展宏的任何使用之前,库A必须被实例化(instantiated)。当库B作为一个程序的部分被最终运行的时候,库A可能不被需要,或者,它可能也被库B的运行时需要。库机制使用阶段来区别这些时间,这会在本小节解释。

每个库可用通过扩展时(expand-time)信息(最低限度地,它的导入的库,导出的关键词的一个列表,导出的变量的一个列表,计算转换表达式的代码)和运行时信息(最低限度地,计算变量定义的右边表达式的代码,计算内部表达式的代码)表现其特征。扩展时信息必须对任何导出绑定的扩展引用可见,且运行时信息必须对任何导出变量绑定的求值引用可见。

一个阶段(phase)是一段时间,在这段时间里库中的表达式被求值。在一个库的内部,顶层表达式和define形式的右边在运行时,也就是阶段0,被求值,且define-syntax形式的右边在扩展时,也就是阶段1,被求值。当define-syntax, let-syntax, 或letrec-syntax出现在在阶段n被求值的代码中时,它们的右边将在阶段n + 1被求值。

这些阶段和库它自己使用的阶段是相关的。库的一个实例(instance)对应于一个与另一个库相关的特定阶段的它的变量定义和表达式的求值过程—一个叫做实例化(instantiation)的过程。比如,如果库B中的一个顶层表达式引用库A中导出的一个变量,那么它在阶段0(相对于B的阶段)从A的一个实例中引用这个导出。但是,B中一个阶段1的表达式引用A中相同的绑定,那么,它在阶段1(相对于B的阶段)从A的一个实例中引用这个导出。

一个库的访问(visit)对应于在一个特定阶段与另一个库相关的语法定义的计算过程—一个叫做访问(visiting)的过程。比如,如果库B中的一个顶层表达式引用来库A导出的一个宏,那么,它在阶段0(相对应B的阶段)从A的访问中引用这个导出,其对应于在阶段1宏转换表达式的求职过程。

一个级别(level)是一个标识符的词法属性,其决定了它可以在哪个阶段被引用。在一个库中,每个用定义绑定的标识符的级别是0;也就是说,在库中只能在阶段0引用这个标识符。除了导出库中标识符的级别之外,每一个导入的绑定的级别由导入库中import的封装的for形式决定。导入和导出级别通过所有级别组合的成对相加的方式组合。比如,一个以\(p_a\)\(p_b\)级别导出,以\(q_a\)\(q_b\)\(q_c\)级别导入的被导入的标识符的引用在以下级别是有效的:\(p_a + q_a\), \(p_a + q_b\), \(p_a + q_c\), \(p_b + q_a\), \(p_b + q_b\), 和\(p_b + q_c\)。一个没有封装的for<import set>等价于(for <import set> run),它和(for <import set> (meta 0))是一样的。

对于所有的定义在导出库中的绑定来说,一个被导出的的绑定的级别是0。一个重新导出的绑带,也就是一个从其它库导入的导出,的级别和重新导出库中的绑定的有效导入级别是一样的。

对于定义在库报告中的库来说,几乎所有的绑定,导出级别是0。例外是来自(rnrs base (6))库的syntax-rules, identifier-syntax, ..., 和_形式以级别1被导出,来自(rnrs base (6))库的set!形式以级别0和1被导出,来自复合(rnrs (6))库(见库的第15章)的所有绑定以级别0和1被导出。

一个库中的宏扩展可以引出标识符的一个引用,其中这个标识符没有显式地导入到这个库中。在这种情况下,引用的阶段必须符合标识符的作为偏移的级别,这个偏移是源库(也就是提供标识符词法上下文的库)和封装引用的库的阶段的不同。比如,假设扩展一个库调用一个宏转换,且宏转换的求值引用一个标识符,这个标识符被从另一个库中被导出(所以,库的阶段1的实例被使用);进一步假设绑定的值是一个表示一个指示级别n绑定的标识符的语法对象;那么,在库被扩展的时候,标识符必须只能在n + 1阶段被使用。这个级别和阶段的组合就是为什么标识符负的级别是有用的,甚至,尽管库只存在非负的阶段。

如果在一个程序的扩展形式中,一个库的定义中的任何一个在阶段0被引用,那么被引用的库的一个作为阶段0实例会在程序的定义和表达式被求值之前被创建。这条规则的应用是透明的:如果一个库的扩展形式在阶段0从另一个库引用一个标识符,那么,在引用库在阶段n被实例化之前,被引用的库必须在阶段n被实例化。当一个标识符在任何大于0的阶段n被引用,那么于此相反,定义库会在阶段n被实例化,其在引用被求值之前的一些未定义的时间。同样地,在一个库的扩展期间,当一个宏关键词在阶段n被引用的时候,定义库在阶段n被访问,其是引用被计算之前的一定未定义的时间。

一个实现可以为不同的阶段区别实例/访问,或者在任何阶段使用一个实例/访问就像在任何其它阶段的一个实例/访问一样。更进一步,一个实现可以扩展每一个库形式以区别在任何阶段的库的访问和/或在大于0的阶段的库的实例。一个实现可以创建更多库的实例/访问在比要求的安全引用的更多的阶段。当一个标识符作为一个表达式出现在一个与标识符级别不一致的阶段时,那么一个实现可以抛出一个异常,异常的抛出可以在扩展时也可以在运行时,或者,它也可以允许这个引用。因此,一个库可能是不可移植的,当其含义依赖于一个库的实例在库的整个阶段或library扩展时是有区别的还是共享的时候。

7.3. 例子

各种<import spec><export spec>的例子:

(注:下面的例子已根据勘误表更正。)

(library (stack)
  (export make push! pop! empty!)
  (import (rnrs)
          (rnrs mutable-pairs))

  (define (make) (list '()))
  (define (push! s v) (set-car! s (cons v (car s))))
  (define (pop! s) (let ([v (caar s)])
                     (set-car! s (cdar s))
                     v))
  (define (empty! s) (set-car! s '())))

(library (balloons)
  (export make push pop)
  (import (rnrs))

  (define (make w h) (cons w h))
  (define (push b amt)
    (cons (- (car b) amt) (+ (cdr b) amt)))
  (define (pop b) (display "Boom! ")
                  (display (* (car b) (cdr b)))
                  (newline)))

(library (party)
  ;; 所有的导出:
  ;; make, push, push!, make-party, pop!
  (export (rename (balloon:make make)
                  (balloon:push push))
          push!
          make-party
          (rename (party-pop! pop!)))
  (import (rnrs)
          (only (stack) make push! pop!) ; 非空的!
          (prefix (balloons) balloon:))

  ;; 以气球的堆栈创建一个派对(party),
  ;; 从两个气球开始
  (define (make-party)
    (let ([s (make)]) ; from stack
      (push! s (balloon:make 10 10))
      (push! s (balloon:make 12 9))
      s))
  (define (party-pop! p)
    (balloon:pop (pop! p))))

(library (main)
  (export)
  (import (rnrs) (party))

  (define p (make-party))
  (pop! p)        ; 显示"Boom! 108"
  (push! p (push (make 5 5) 1))
  (pop! p))       ; 显示"Boom! 24"

宏和阶段的例子:

(library (my-helpers id-stuff)
  (export find-dup)
  (import (rnrs))

  (define (find-dup l)
    (and (pair? l)
         (let loop ((rest (cdr l)))
           (cond
            [(null? rest) (find-dup (cdr l))]
            [(bound-identifier=? (car l) (car rest))
             (car rest)]
            [else (loop (cdr rest))])))))

(library (my-helpers values-stuff)
  (export mvlet)
  (import (rnrs) (for (my-helpers id-stuff) expand))

  (define-syntax mvlet
    (lambda (stx)
      (syntax-case stx ()
        [(_ [(id ...) expr] body0 body ...)
         (not (find-dup (syntax (id ...))))
         (syntax
           (call-with-values
               (lambda () expr)
             (lambda (id ...) body0 body ...)))]))))

(library (let-div)
  (export let-div)
  (import (rnrs)
          (my-helpers values-stuff)
          (rnrs r5rs))

  (define (quotient+remainder n d)
    (let ([q (quotient n d)])
      (values q (- n (* q d)))))
  (define-syntax let-div
    (syntax-rules ()
     [(_ n d (q r) body0 body ...)
      (mvlet [(q r) (quotient+remainder n d)]
        body0 body ...)])))