Skip to content

Latest commit

 

History

History
684 lines (491 loc) · 31.4 KB

racket_ref.md

File metadata and controls

684 lines (491 loc) · 31.4 KB

racket reference

计算模型,Evaluation Model

Racket计算可以被看作是对表达式的化简以获得. 例如, 就像小学生化简

1 + 1 = 2

Racket 计算化简

(+ 1 1) → 2

箭头取代了更传统的=, 以强调计算是朝着更简单的表达式的特定方向进行的. 特别地, (value), 如数字2, 是无法通过计算继续化简的 表达式.

子表达式计算和continuation,Sub-expression Evaluation and Continuations

有些简化需要一个以上的步骤. 比如说.

(- 4 (+ 1 1)) → (- 4 2) → 2

一个不是表达式总是可以被分割成两部分: redex("可简化表达式"), 这是单步简化中可以改变的部分, 例如(+ 1 1). 以及 continuation, 也就是包裹 redex 的计算环境. 在(- 4 (+ 1 1))中, redex 是(+ 1 1), continuation(- 4 []), 其中[]代替redex的位置, 它要被简化. 也就是说, continuation 说的是在redex被化简到之后如何 继续 计算.

在某些表达式被计算之前, 它们的一些或全部子表达式必须被计算. 例如, 在application (- 4 (+ 1 1)) 中, 在 子表达式(+ 1 1)被化简之前, -的应用不能被化简. 因此, 每个syntactic 形式的规范都指定了如何计算其子表达式, 然后如何将结果结合起来以化简该形式.

表达式dynamic extent 是该表达式的计算步骤序列, 过程中表达式包含 redex.

尾部位置,Tail Position

expr2 包围着 expr1, 当expr1 被化简成 redex 之后, 如果expr1continuation, 与expr2continuation相同, 那么就称作: 表达式expr1 处于 expr2 的尾部位置. 直白点说, 就是该做的化简都差不多了, expr1可以跳出这层结构.

例如, (+1 1) 表达式相对于 (- 4 (+1 1)) 来说不处于尾部位置. 为了说明这点, 我们使用符号C[... expr]来表示, 在某个continuation C 中用 expr 代替 [] 得到的结果.

C[(- 4 (+ 1 1)) ] → C[(- 4 2)]

在这种情况下, 化简 (+1 1)之后, 接下来的 continuationC[(- 4 [])] , 而外层的continuation 仅仅是 C, 内外层的 continuation 不相同, 所以说 (+1 1) 不在尾部位置.

相反, 我们可以说 (+ 1 1) 处于(if (zero? 0) (+ 1 1) 3) 的尾部位置, 因为对于任何continuation C.

C[(if (zero? 0) (+ 1 1) 3)] → C[(if #t (+ 1 1) 3)] → C[(+ 1 1)]

内外层的continuation 相同, 上述规定的要求得到了满足. 这个化简序列的步骤是由if的定义驱动的, 它们不依赖于continuation C 的具体形式. 事实上, if形式的 then 分支相对于if形式总是处于尾部位置. 由于if#f的类似化简规则, if 形式的 else 分支也在 尾部位置.

尾部位置规范提供了关于计算的渐进空间消耗(asymptotic space consumption )的保证. 一般来说, 尾部位置的指定伴随着每个syntactic 形式的描述, 比如 if.

垃圾收集

  • 参见内存管理,了解与垃圾收集有关的功能.

在某时刻的程序状态,

objects: (define <o1> (vector 10 20))
                (define <o2> (vector 0))
defined: (define x <o1>)
evaluate: (+ 1 x)

计算不能依赖于<o2>,因为它不是要计算的程序的一部分,而且它没有被程序可以访问的任何定义所引用. 这个对象被说成是不可达的. 因此,对象<o2>可以通过垃圾收集从程序状态中删除.

一些特殊的复合数据类型持有指向对象的弱引用. 垃圾收集器在确定哪些对象在剩余的计算中是可到达的时候,会特别处理这种弱引用. 如果对象只能通过弱引用到达,那么这个对象可以被回收, 弱引用被一个不同的值(通常是#f)取代.

作为特例, fixnum总是被垃圾收集器认为是可到达的. 许多其他的值由于它们的实现和使用方式,总是可以达到的. 在Latin-1范围内的字符总是可达的, 因为equal? Latin-1字符总是eq?, 而且所有的Latin-1字符都被一个内部模块所引用. 同样,null,#t, #f, eof#<void> 也总是可达的. 当quote表达式本身可达时, 由quote产生的值仍然可达.

程序应用和局部变量,Procedure Applications and Local Variables

给出

f(x) = x + 10

代数学生会将f(7)简化如下.

f(7) = 7 + 10 = 17

这个简化的关键步骤是将定义的函数f的主体, 用实际值7替换每个x. Racket 过程应用(Procedure Applications)的工作方式与此基本相同. 过程是一个对象,所以计算(f 7)要从一个变量的查找开始.

objects: (define <p1> (lambda (x) (+ x 10)))
defined: (define f <p1>)
evaluate: (f 7)

→objects:(define <p1> (lambda (x) (+ x 10)))
defined:(define f <p1>)
evaluate:(<p1> 7)

然而,与代数不同的是, 与过程参数变量相关的值, 可以在过程的主体中通过使用set!来改变, 如例子中的(lambda (x) (begin (set! x 3) x)). 由于与参数变量x相关的值应该是可以改变的, 所以我们不能在第一次应用过程时直接用这个值来代替x.

我们不使用 参数变量 (arameter variable) 这一术语, 来指代与函数一起声明的参数变量名(argument variable names). 这样可以避免与parameters的混淆.

取而代之的是,在每次application中为变量创建新的位置(location). 参数value被放置在这个位置上, 而程序主体中的每个变量实例都被替换成新的位置.

objects: (define <p1> (lambda (x) (+ x 10)))
defined: (define f <p1>)
evaluate: (<p1> 7)

→objects:(define <p1> (lambda (x) (+ x 10)))
defined:    (define f <p1>)
                    (define xloc 7)
evaluate:(+ xloc 10)

→objects: (define <p1> (lambda (x) (+ x 10)))
defined: (define f <p1>)
                    (define xloc 7)
evaluate: (+ 7 10)

→objects: (define <p1> (lambda (x) (+ x 10)))
    defined: (define f <p1>)
                    (define xloc 7)
    evaluate:17

位置顶层变量(top-level variable)是一样的, 但是当位置被生成时,它(在概念上)使用了之前没有被使用过的名字, 并且不能再次生成直接访问.

以这种方式生成位置意味着set!对局部变量(包括参数变量)的计算方式, 与对顶层变量的计算方式相同, 因为在set!形式被计算时, 局部变量总是被替换成位置.

        objects: (define <p1> (lambda (x) (begin (set! x 3) x)))
        defined: (define f <p1>)
        evaluate: (f 7)

→ objects: (define <p1> (lambda (x) (begin (set! x 3) x)))
    defined: (define f <p1>)
    evaluate: (<p1> 7)

→objects: (define <p1> (lambda (x) (begin (set! x 3) x)))
    defined: (define f <p1>)
                    (define xloc 7)
    evaluate: (begin (set! xloc 3) xloc)

→objects: (define <p1> (lambda (x) (begin (set! x 3) x)))
    defined:  (define f <p1>)
                        (define xloc 3)
    evaluate: (begin (void) xloc)

→objects: (define <p1> (lambda (x) (begin (set! x 3) x)))
    defined: (define f <p1>)
                        (define xloc 3)
    evaluate:  xloc

→objects: (define <p1> (lambda (x) (begin (set! x 3) x)))
    defined: (define f <p1>)
                        (define xloc 3)
    evaluate: 3

程序作用时, 位置生成和 替换步骤要求参数是一个. 因此,在((lambda (x) (+ x 10)) (+ 1 2))中, (+ 1 2) 子表达式必须能被化简到值3, 然后3可以被放置到x的位置. 换句话说, Racket 是值调用 语言. (call-by-value language)

局部变量形式的计算,如 (let ([x (+ 1 2)]) expr),与procedure调用是一样的. 在(+1 2)产生后, 它被存储到新的location, location将取代exprx的每个实例.

在这里, location 表示局域变量的意思, 并不是指通常的位置.

变量和位置,Variables and Locations

变量占位符, 初始程序中的表达式指向变量.

顶层变量既是变量又是位置. 任何其他变量在运行时总是被位置取代; 因此, 表达式的计算只涉及位置. 单一的局部变量(即非顶层,非模块级的变量), 如参数变量, 在不同的applications中可以对应不同的位置. 例如,在程序

(define y (+ (let ([x 5]) x) 6))

中, yx都是变量. y变量是顶层变量, 而x是局部变量. 当这段代码被计算时,为x创建了存放数值5位置, 同时也为y创建了存放数值11的位置.

在计算过程中, 用位置替换变量实现了Racket文法作用域(lexical scoping). 例如,当参数变量x被位置xloc替换时, 整个过程主体中的x都将被替换, 包括任何嵌套的 lambda 形式. 因此, 将来对该变量引用总是访问同一个位置.

为了实现用xloc替换掉x, 所有的变量绑定都必须使用不同的名字, 从而保证不会替换掉实际上不同的变量x. 确保这种区别是宏展开器(macro expander)的工作之一; 见 Syntax Model.

语法模型,Syntax Model

Racket程序的语法是由以下部分定义的;

  • read pass, 将字符流处理成语法对象; 以及
  • expand pass, 对语法对象进行处理, 产生完全解析的语法对象.

关于read通道的细节, 请参见 The Reader. 源代码通常是在 read-syntax 模式下读取的, 它产生syntax object.

expand通道递归地处理语法对象, 产生完整的程序解析. 语法对象中的绑定信息驱动展开过程, 当展开过程遇到绑定形式时, 它用新的绑定信息展开子表达式的语法对象.

标识符, 绑定和作用域

标识符(identifier )是源代码中的实体(entity). 解析 (即展开) Racket 程序会发现, 一些标识符对应于变量(variables), 一些指的是语法形式(如 lambda, 它是函数的语法形式), 一些指的是用于宏扩展的转化器(transformers), 还有一些被quoted来产生 symbolssyntax objects. 当标识符A被解析为变量语法形式, 而标识符B被解析为对A引用时, 标识符A就会绑定标识符B(即binding); 后者被绑定(bound).

例如, 作为源码的一个片段, 该文本

(let ([x 5]) x)

包括两个标识符: letx(出现两次). 当这个源码按照let 通常的含义被解析时, 第一个x绑定了第二个x.

绑定(bindings )和引用(references )是通过范围集(scope sets)确定的. 范围(scope)对应于程序的区域, 这个区域要么是源代码的一部分, 要么是通过对源代码的阐述而合成的. 嵌套绑定的上下文(如嵌套的函数) 创造嵌套的作用域, 而宏展开创造了以更复杂方式重叠的作用域. 从概念上讲, 每个作用域都由唯一的标记(token)表示, 但这个标记是不能直接访问的. 相反, 每个作用域由一个值来表示, 这个值对于程序的表示是内部的. each scope is represented by a value that is internal to the representation of a program

form是程序的片段, 如标识符函数调用. form被表示为语法对象, 每个语法对象都有一个相关的作用域集(即scope set). 在上面的例子中, x 的表示包括与let形式对应的scope.

form解析为特定标识符的绑定时, 解析会更新一个全局 Table, 该Table标识符符号(symbol)和作用域集的组合映射到其含义上: 也就是变量, 语法形式(syntactic form)或转化器. 当引用(reference)的symbol 标识符symbol相同, 且引用的 scope set绑定的 scope set超集(superset)时, 标识符就指向这个特定的绑定.

对于给定的标识符, 可能有多个绑定scope set标识符 scope set 的子集; 在这种情况下, 标识符指向的绑定, 是具有最大scope set的绑定, 即后者是所有其他scope set的超集; 如果不存在这样的绑定, 引用(此次调用)就是模糊的(如果它被解析为表达式, 会触发一个语法错误). 作用域大的绑定掩盖(shadow)作用域小的绑定, 如果他们具有相同的symbol .

注意: 一般越内层的变量/绑定, 它的作用域越大, 跟源代码的直观形式是刚好相反的. 例如, 在

(let ([x 5]) x)

中, let对应于通常的语法形式, let的解析为x绑定引入了新的作用域. 由于第二个x作为let主体的一部分, 接收到了let的作用域, 第一个x binds 第二个x. 更复杂的情形为,

(let ([x 5])
    (let ([x 6])
        x))

内部 let 为第二个x 创建了第二个作用域, 所以它的作用域集是第一个 x 作用域集的超集 --这意味着第二个 x 的绑定会掩盖第一个 x 的绑定, 从而第三个 x 指向的是, 第二个x创建的绑定.

顶层绑定是在顶层的定义确立的绑定; 模块绑定是模块中的定义确立的绑定; 所有其他绑定是本地绑定(local bindings). 在模块内, 不允许引用顶层绑定. 没有任何 绑定标识符 is unbound.

在整个文档中, 标识符的命名表明了它们被解析的方式. 像 lambda这 样的超链接标识符, 表示它是对syntactic 形式变量引用. 像x这样的普通标识符是一个变量, 或对某个非指定顶层变量的引用.

每个绑定都有阶段(phase level), 在这个阶段可以引用此绑定. 相位通常对应于一个整数(但特殊label phase level并不对应于整数). phase level 0对应于enclosing 模块run time(或顶层表达式的run time). 处于 phase level 0的绑定构成base environment.

阶段1 对应于enclosing 模块(或顶层表达式)被展开的时刻; 阶段1 中的绑定构成transformer environment. 阶段-1 是另一个模块Brun time, enclosing 模块阶段1(相对于 importing 模块)被导入模块B使用; (也就是父模块) 阶段-1 中的绑定构成template environment. label phase level不对应任何execution time; 它被用来跟踪bindings (例如, 文档中出现的标识符), 但它不意味着执行时的依赖.

标识符在不同的阶段 可以具有不同的绑定. 更确切地说, 与一个form相关的scope set在不同的阶段可以是不同的;

top-level模块上下文在每个阶段都意味着不同的作用域, 而在所有阶段, 来自宏展开或其他语法形式作用域都被添加到form作用域集. 每个绑定引用上下文, 决定了phase level, 后者又决定了前者的scope set.

在软件包base的6.3版本中进行了修改. 改变了本地绑定, 使其具有特定的阶段, 就像顶级绑定模块绑定一样.

语法对象,Syntax Objects

语法对象将较简单的Racket值(如symbol pair)与lexical 信息, source-location信息, syntax propertiestamper status相结合. 语法对象lexical information包括一组范围集, 每个阶段都有一个. 特别是, 标识符被表示为包含symbol的语法对象, 它的lexical 信息可以与全局绑定表相结合, 以确定它在每个阶段binding(如果有的话).

例如, car 标识符可能有lexical 信息, 将其指定为racket/base语言中的car(即内置car). 同样地, lambda 标识符的lexical 信息可能表明它代表一个procedure 形式. 其他标识符lexical 信息可能表明它引用了顶层变量.

语法对象代表一个比标识符简单常量更复杂的表达式时, 它的内部组件可以被提取出来. 即使对于提取出的标识符, 关于绑定的详细信息也大多是间接可用的; 两个标识符可以进行比较, 以确定它们是否引用了相同的绑定(即free-identifier=?), 或者标识符是否具有相同的scope set, 以便如果标识符A处于binding 位置, 标识符B处于expression 位置, 则A应该bind标识符B(bound-identifier=?)

例如, 当程序,

(let ([x 5]) (+ x 6))

被表示为语法对象, 那么就可以为两个x提取两个语法对象. free-identifier=?bound-identifier=? 谓词都将表明这两个x是相同的. 相反, let 标识符对任何一个x都不是 free-identifier=?bound-identifier=?.

语法对象中的lexical 信息是独立于语法对象的其他部分的, 它可以和任意的其他Racket值一起被复制到新的语法对象中. 因此, 语法对象中的标识符绑定信息是由标识符的符号名称以及标识符的lexical信息共同决定的的; 同一个问题, 如果有相同的lexical 信息, 但base value 不同, 就会产生不同的答案.

例如, 将上面程序中letlexical 信息 结合到'x, 不会产生与任一x相同绑定的标识符(通过 free-identifier=? 判断), 因为它没有出现在x绑定的范围内. 相反, 将6lexical 信息'x结合起来, 会产生一个和两个x具有相同绑定的标识符( 通过bound-identifier=? ).

Quote-syntax形式, 连接了程序的计算(evaluation)和程序的表示(representation ). 具体来说, (quote-syntax datum #:local)产生一个语法对象, 它保留了 datum 的所有lexical 信息, 当datum 作为 quote-syntax 形式的一部分被解析(parsed )时.

请注意, (quote-syntax datum)形式是类似的, 但它从datumscope sets中删除了某些scopes; 更多信息见quote-syntax.

展开,Expansion (Parsing)

Expansion (Parsing)

Expansion 以递归方式处理特定阶段语法对象, 从阶段0开始. 语法对象lexical 信息Binding推动了展开过程, 并导致引入新绑定子表达式lexical 信息中. 在某些情况下, 子表达式被展开到比enclosing 表达式更深的阶段(具有更大的阶段数).

The Reader

Delimiters and Dispatch

The Reader

除了空格BOM字符外, 以下字符也是分隔符.

    ( ) [ ] { } " , ' ` ;

以任何其他字符开始的, 被定界的序列通常被解析为符号(symbol), 数字extflonum, 但有几个非定界字符起着特殊作用.

  • #作为划线序列中的初始字符具有特殊意义;其意义取决于后面的字符;见下文.
  • |开始一个子序列,该子序列将被逐字包含在划定的序列中(也就是说,它们从不被视为分界符,当启用大小写不敏感时,它们不会被折算); 该子序列由另一个|终止,并且初始和终止的|都不是该子序列的一部分.
  • \ 在一个|对之外,导致下面的字符被逐字包括在一个限定的序列中. 更确切地说,在跳过空白#uFEFF BOM字符后, reader 根据输入流中的下一个或多个字符进行分配,如下所示.

数据类型,Datatypes

Equality

Equality 是指两个 values 是否 相同 的概念. Racket默认支持几种不同的equality , 但在大多数情况下, equal?是首选.

(equal? v1 v2) → boolean?
    v1 : any/c
    v2 : any/c

当且仅当它们是eqv?时, 两个值是equal?的, 除非对特定的数据类型另有规定.

equal?有进一步说明的数据类型包括 字符串, 字节字符串, , 可变对, 向量, 盒子, 哈希表可检查结构(inspectable structures).

对于后六种情况, equality是递归定义的; 如果v1v2都包含引用循环, 那么当值的无限展开是equal的, 它们就是equal的. 另见 gen:equal+hash 和 prop:impersonator-of . 例子.

> (equal? 'yes 'yes)
#t
> (equal? 'yes 'no)
#f
> (equal? (* 6 7) 42)
#t
> (equal? (expt 2 100) (expt 2 100))
#t
> (equal? 2 2.0)
#f
> (let ([v (mcons 1 2)]) (equal? v v))
#t
> (equal? (mcons 1 2) (mcons 1 2))
#t
> (equal? (integer->char 955) (integer->char 955))
#t
> (equal? (make-string 3 #\z) (make-string 3 #\z))
#t
> (equal? #t #t)
#t

(eqv? v1 v2) → boolean?

v1 : any/c v2 : any/c

当且仅当两个值是eq?时, 它们就是eqv?, 除非对特定数据类型另有规定.

数字(number)和字符(character)数据类型是eqv?eq?不同的数据类型.

当两个数字具有相同的精确性, 精度, 并且都是相等且非零, 或者都是+0.0, 都是+0.0f0, 都是-0.0, 都是-0.0f0, 都是+nan.0, 或者都是+nan.f 在复数的情况下分别考虑实部虚部, 从而返回它们的eqv?结果.

当两个字符的char->integer结果相等时, 它们eqv?.

一般来说, eqv?equal?相同, 只是eqv?不能递归比较复合数据类型(如lists structs)的内容, 也不能由用户定义的数据类型来定制. 我们不鼓励使用eqv?, 而是使用equal?. 例子.

> (eqv? 'yes 'yes)
#t
> (eqv? 'yes 'no)
#f
> (eqv? (* 6 7) 42)
#t
> (eqv? (expt 2 100) (expt 2 100))
#t
> (eqv? 2 2.0)
#f
> (let ([v (mcons 1 2)]) (eqv? v v))
#t
> (eqv? (mcons 1 2) (mcons 1 2))
#f
> (eqv? (integer->char 955) (integer->char 955))
#t
> (eqv? (make-string 3 #\z) (make-string 3 #\z))
#f
> (eqv? #t #t)
#t
(eq? v1 v2) → boolean?
    v1 : any/c
    v2 : any/c

如果v1v2指向同一个对象, 返回#t, 否则返回#f. 数字作为一个特例, 对于eq?, 两个=fixnums也是相同的. 参见 Object Identity and Comparisons. 例子.

> (eq? 'yes 'yes)
#t
> (eq? 'yes 'no)
#f
> (eq? (* 6 7) 42)
#t
> (eq? (expt 2 100) (expt 2 100))
#f
> (eq? 2 2.0)
#f
> (let ([v (mcons 1 2)]) (eq? v v))
#t
> (eq? (mcons 1 2) (mcons 1 2))
#f
> (eq? (integer->char 955) (integer->char 955))
#t
> (eq? (make-string 3 #\z) (make-string 3 #\z))
#f
> (eq? #t #t)
#t
(equal?/recur v1 v2 recur-proc) → boolean?
    v1 : any/c
    v2 : any/c
    recur-proc : (any/c any/c -> any/c)

equal?相似,但使用recur-proc进行递归比较(这意味着不会自动处理引用循环). 来自recur-proc的非#f结果在被equal?/recur返回之前被转换为#t. 例子.

> (equal?/recur 1 1 (lambda (a b) #f))
#t
> (equal?/recur '(1) '(1) (lambda (a b) #f))
#f
> (equal?/recur '#(1 1 1) '#(1 1.2 3/4)
                (lambda (a b) (<= (abs (- a b)) 0.25)))
#t

文档的记号,Notation

本章介绍了整个Racket文档中使用的基本术语和符号.

模块文档的注释

由于Racket程序被组织成模块(modules), 文档反映了这种组织, 在sectionsubsection的开头有一个注释, 描述了特定模块提供的绑定.

例如, 描述由 racket/list 提供的功能的部分开始于

(require racket/list)   package: base

有些模块是用 #lang 引入的, 而不是 require:

#lang racket/base   package: base

使用 #lang 意味着该模块通常被用作整个模块的语言--也就是说, 由模块开始的 #lang 后面是语言, 而不是用require导入. 然而, 除非另有规定, 用 #lang 记录的模块名称也可以用 require 来获得语言的绑定.

模块注解(annotation)还在右侧显示了该模块所属的package. 关于package的更多细节, 请参阅Racket中的包管理.

有时, 模块说明会出现在文档的开头, 或者出现在包含许多子节的章节开头. 文件的章节或章节的子部分, 继承了外层文档章节的模块声明. 因此, 除非在章节小节中另有规定, 否则在The Racket Reference中记录的绑定可以从racketracket/base中获得.

Syntactic 形式文档的记号

Racket指南中的 Notation 介绍了语法形式的这种记号.

Syntactic 形式是用grammar(语法)来指定的. 通常情况下, 语法以开放的小括号开始, 后面是语法形式的名称, 如 if 的语法.

(if test-expr then-expr else-expr) syntax

由于每一种形式都是用语法对象来表达的, 语法规范中的小括号表示包裹(wrapping)着一个列表的语法对象, 前面的 if 是一个标识符, 它开启了这个列表. 其绑定是被记录的模块if绑定--在本例中是 racket/base. 语法中的方括号小括号一样表示语法对象列表, 但在程序源码中, 通常是按惯例使用方括号.

语法中的斜体标识符是对应于其他语法产生的元变量(metavariables). 某些元变量名称对应隐含的语法生成.

  • id结尾的元变量代表标识符.
  • keyword结尾的元变量代表一个语法对象关键词.
  • expr结尾的元变量代表任何形式, 并且该形式将作为表达式解析.
  • body结尾的元变量代表任何形式; 该形式将被作为局部定义表达式解析. body只有在前面没有任何表达式的情况下才能解析为定义, 而且最后一个body必须是表达式; 另见Internal Definitions.
  • datum 结尾的元变量代表任何形式, 而且这个形式通常是不被解析的(例如, 被quote的形式).
  • numberboolean 结尾的元变量分别代表任何语法对象(即字面值的)numberboolean.

在语法中, 形式 ... 代表与形式相匹配的任何数量的形式(可能是), 而形式 ...+ 代表与形式相匹配的一个或多个形式.

没有隐含语法的元变量, 由句法形式的整体语法旁边的生成方式 来定义. 例如, 在

(lambda formals body ...+)                          syntax
formals     =   id
                    |   (id ...)
                    |   (id ...+ . rest-id)

formals 元变量代表单个标识符, 或语法对象列表中的零个或多个标识符, 或者是一个或多个对组成的链, 的末端是标识符而不能是空列表.

有些句法形式有多个顶层语法, 在这种情况下, 句法形式的文档会显示多个语法. 比如说

(init-rest id)      syntax
(init-rest)

表示init-rest既可以在其语法对象列表中单独出现, 也可以在后面加上一个标识符.

最后, 包括expr元变量的语法规范, 可以含有对一些元变量运行时契约(run-time contract), 这些契约表示, 表达式的结果在运行时必须满足的谓词. 比如说

(parameterize ([parameter-expr value-expr] ...)                     syntax
    body ...+)
parameter-expr      :   parameter?

表示每个parameter-expr的结果必须是这样的值v, 对于这个值(parameter? v)返回真.

函数文档的符号

程序(procedure)和其他是用基于契约(contract)的记号来描述的. 从本质上讲, 这些契约使用Racket谓词和表达式来描述在档接口.

例如, 下面是一个典型过程定义的标题(header).

(char->integer char) → exact-integer?                               procedure
char : char?

被定义的函数, char->integer, 像被应用时那样排版. 在函数名后面的元变量代表了参数. 角落里的白色文字标识了被记录的种类(kind).

每个元变量都用一个契约来描述. 在前面的例子中, 元变量char的契约是char?. 这个契约规定, 任何被 char? 谓词判断为true的参数char都是有效的. 文档中的函数可能会, 也可能不会实际检查这个属性, 但是契约表明了实现者的意图(intent).

箭头右边的契约, 即本例中的exact-integer?, 指定了由函数产生的预期结果.

契约规范可以比只有谓词名称更有表达力. 请看下面这个argmax的标题.

(argmax proc lst) → any                             procedure
proc : (-> any/c real?)
lst : (and/c pair? list?)

契约(-> any/c real?)表示一个函数契约, 指定proc的参数可以是任何单个值, 结果应该是一个实数. lst的契约(and/c pair? list?)指定lst应该同时传递pair?list? (即它不能是空列表).

->and/c都是契约组合器(contract combinator)的例子. 契约组合器如or/c, cons/c, listof 和其他的组合器在整个文档中都被使用. 点击组合器名称的超链接将提供更多关于其含义的信息.

Racket函数可能被记录为有一个或多个可选参数. read 函数就是一个这样的例子:

(read [in]) → any                                   procedure
in : input-port? = (current-input-port)

应用形式的语法中, in参数周围的方括号(brackets)表示它是可选参数.

read的 头部条 像往常一样为参数in指定了一个契约. 在契约的右边, 它还指定了默认值(current-input-port), 如果read被调用时没有参数, 就会使用这个值.

函数也可以被记录为接受强制性可选的基于关键字参数. 例如, sort 函数有两个可选的, 基于关键字的参数.

(sort   lst
            less-than?
            [ #:key extract-key
            #:cache-keys? cache-keys?])     →     list?

    lst : list?
    less-than? : (any/c any/c . -> . any/c)
    extract-key : (any/c . -> . any/c) = (lambda (x) x)
    cache-keys? : boolean? = #f

和之前一样, extract-keycache-keys? 参数周围的方括号表示它们是可选的. 头部条的契约部分显示了为这些关键字参数提供的默认值.

结构类型的符号

结构类型(structure type)也是用契约符号来记录的.

(struct color (red green blue alpha))                                       struct
    red : (and/c natural-number/c (<=/c 255))
    green : (and/c natural-number/c (<=/c 255))
    blue : (and/c natural-number/c (<=/c 255))
    alpha : (and/c natural-number/c (<=/c 255))

结构类型的排版, 跟它在程序源代码中使用struct形式的声明一样. 结构的每个字段都有一个相应的契约, 规定了该字段可接受的.

在上面的例子中, 结构类型 color 有四个字段: red, green, bluealpha. 该结构类型构造函数接受满足(and/c natural-number/c (<=/c 255))的字段值, 即255以内的非负精确整数.

结构类型的文档中, 额外的关键字可能出现在字段名之后.

(struct     data-source (connector args extensions)                             struct
    #:mutable)
connector : (or/c 'postgresql 'mysql 'sqlite3 'odbc)
args : list?
extensions : (listof (list/c symbol? any/c))

在这里, #:mutable 关键字表示, data-source结构类型的实例的字段, 可以用各自的setter 函数进行修改(mutate).

参数

parameter的记录方式与函数的记录方式相同.

(current-command-line-arguments) → (vectorof string?)                               parameter
(current-command-line-arguments argv) → void?
    argv : (vectorof (and/c string? immutable?))

由于参数可以被引用设置, 上面的 header 有两个条目. 在没有参数的情况下调用 current-command-line-arguments 可以访问参数的值, 它必须是一个向量, 其元素 需要同时通过 string?immutable?. 使用单参数调用 current-command-line-arguments, 将设定参数的值, 这个值必须是向量, 其元素必须通过string? (如果需要的话, parameter上的防护措施, 可以将字符串转型为不可变形式).

其他文档的符号

一些(library)提供了与常量值的绑定. 这些用单独的头来记录.

object% : class?                    value

racket/class库提供了 object% 值, 它是Racket中类层次结构的(root of the class hierarchy). 它的文档标头只是表明, 它是满足谓词 class? 的值.