Racket
计算可以被看作是对表达式
的化简以获得值
. 例如, 就像小学生化简
1 + 1 = 2
Racket
计算化简
(+ 1 1) → 2
箭头→
取代了更传统的=
, 以强调计算是朝着更简单的表达式的特定方向进行的.
特别地, 值
(value), 如数字2
, 是无法通过计算继续化简的 表达式
.
有些简化
需要一个以上的步骤. 比如说.
(- 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
.
设expr2
包围着 expr1
, 当expr1
被化简成 redex
之后,
如果expr1
的continuation
, 与expr2
的continuation
相同,
那么就称作: 表达式expr1
处于 expr2
的尾部位置.
直白点说, 就是该做的化简都差不多了, expr1
可以跳出这层结构.
例如, (+1 1)
表达式相对于 (- 4 (+1 1))
来说不处于尾部位置
.
为了说明这点, 我们使用符号C[... expr]
来表示, 在某个continuation C
中用 expr
代替 []
得到的结果.
C[(- 4 (+ 1 1)) ] → C[(- 4 2)]
在这种情况下, 化简 (+1 1)
之后, 接下来的 continuation
是 C[(- 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
产生的值仍然可达.
给出
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
将取代expr
中x
的每个实例.
在这里, location
表示局域变量的意思, 并不是指通常的位置
.
变量
是值
的占位符
, 初始程序中的表达式
指向变量
.
顶层变量
既是变量
又是位置
. 任何其他变量
在运行时总是被位置
取代;
因此, 表达式的计算只涉及位置
. 单一的局部变量
(即非顶层
,非模块级
的变量), 如参数变量
, 在不同的applications
中可以对应不同的位置
.
例如,在程序
(define y (+ (let ([x 5]) x) 6))
中, y
和x
都是变量. y
变量是顶层变量
, 而x
是局部变量.
当这段代码被计算时,为x
创建了存放数值5
的位置
, 同时也为y
创建了存放数值11
的位置.
在计算过程中, 用位置
替换变量
实现了Racket
的文法作用域
(lexical scoping).
例如,当参数变量x
被位置xloc
替换时, 整个过程
的主体
中的x
都将被替换, 包括任何嵌套的 lambda
形式.
因此, 将来对该变量
的引用
总是访问同一个位置
.
为了实现用xloc
替换掉x
, 所有的变量绑定
都必须使用不同的名字, 从而保证不会替换掉实际上不同的变量x
.
确保这种区别是宏展开器
(macro expander)的工作之一; 见 Syntax Model.
Racket
程序的语法是由以下部分定义的;
read
pass, 将字符流
处理成语法对象
; 以及expand
pass, 对语法对象进行处理, 产生完全解析的语法对象
.
关于read
通道的细节, 请参见 The Reader. 源代码通常是在 read-syntax
模式下读取的, 它产生syntax object
.
expand
通道递归地处理语法对象
, 产生完整的程序解析.
语法对象中的绑定信息
驱动展开
过程, 当展开
过程遇到绑定
形式时, 它用新的绑定
信息展开子表达式的语法对象
.
标识符
(identifier )是源代码中的实体
(entity).
解析 (即展开) Racket
程序会发现, 一些标识符
对应于变量
(variables), 一些指的是语法形式
(如 lambda
, 它是函数的语法形式),
一些指的是用于宏扩展的转化器
(transformers), 还有一些被quoted
来产生 symbols
或 syntax objects
.
当标识符A
被解析为变量
或语法形式
, 而标识符B
被解析为对A
的引用
时, 标识符A
就会绑定标识符B
(即binding); 后者被绑定
(bound).
例如, 作为源码
的一个片段, 该文本
(let ([x 5]) x)
包括两个标识符
: let
和x
(出现两次). 当这个源码按照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
是另一个模块B
的run 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版本中进行了修改. 改变了本地绑定, 使其具有特定的阶段
, 就像顶级绑定
和模块绑定
一样.
语法对象
将较简单的Racket值
(如symbol
或pair
)与lexical 信息
, source-location
信息, syntax properties
和tamper 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
不同, 就会产生不同的答案.
例如, 将上面程序中let
的 lexical 信息
结合到'x
, 不会产生与任一x
相同绑定的标识符(通过 free-identifier=?
判断),
因为它没有出现在x
绑定的范围内.
相反, 将6
的 lexical 信息
与'x
结合起来, 会产生一个和两个x
具有相同绑定的标识符( 通过bound-identifier=?
).
Quote-syntax
形式, 连接了程序的计算
(evaluation)和程序的表示
(representation ).
具体来说, (quote-syntax datum #:local)
产生一个语法对象
, 它保留了 datum
的所有lexical 信息
,
当datum
作为 quote-syntax
形式的一部分被解析(parsed )时.
请注意, (quote-syntax datum)
形式是类似的, 但它从datum
的scope sets
中删除了某些scopes
;
更多信息见quote-syntax
.
Expansion
以递归方式处理特定阶段
的语法对象
, 从阶段0
开始.
语法对象
的lexical 信息
的Binding
推动了展开过程, 并导致引入新绑定
到子表达式
的lexical 信息
中.
在某些情况下, 子表达式
被展开到比enclosing 表达式
更深的阶段
(具有更大的阶段数
).
除了空格
和BOM
字符外, 以下字符也是分隔符
.
( ) [ ] { } " , ' ` ;
以任何其他字符开始的, 被定界的序列
通常被解析为符号
(symbol), 数字
或extflonum
, 但有几个非定界字符起着特殊作用.
#
作为划线序列中的初始字符具有特殊意义;其意义取决于后面的字符;见下文.|
开始一个子序列,该子序列将被逐字包含在划定的序列中(也就是说,它们从不被视为分界符,当启用大小写不敏感时,它们不会被折算); 该子序列由另一个|
终止,并且初始和终止的|
都不是该子序列的一部分.\
在一个|
对之外,导致下面的字符被逐字包括在一个限定的序列中. 更确切地说,在跳过空白
和#uFEFF
BOM字符后,reader
根据输入流中的下一个或多个字符进行分配,如下所示.
Equality
是指两个 values 是否 相同
的概念. Racket
默认支持几种不同的equality
, 但在大多数情况下, equal?
是首选.
(equal? v1 v2) → boolean?
v1 : any/c
v2 : any/c
当且仅当它们是eqv?
时, 两个值是equal?
的, 除非对特定的数据类型
另有规定.
对equal?
有进一步说明的数据类型包括 字符串
, 字节字符串
,
对
, 可变对
, 向量
, 盒子
, 哈希表
和 可检查结构
(inspectable structures).
对于后六种情况, equality
是递归定义的;
如果v1
和v2
都包含引用循环, 那么当值的无限展开是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
如果v1
和v2
指向同一个对象, 返回#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
本章介绍了整个Racket文档中使用的基本术语
和符号.
由于Racket
程序被组织成模块
(modules), 文档反映了这种组织,
在section
或subsection
的开头有一个注释
, 描述了特定模块
提供的绑定.
例如, 描述由 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
中记录的绑定可以从racket
和racket/base
中获得.
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
的形式). - 以
number
或boolean
结尾的元变量
分别代表任何语法对象(即字面值的)number
或boolean
.
在语法中, 形式 ...
代表与形式相匹配的任何数量的形式(可能是零
), 而形式 ...+
代表与形式相匹配的一个或多个形式.
没有隐含语法的元变量
, 由句法形式的整体语法旁边的生成方式
来定义. 例如, 在
(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-key
和 cache-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
, blue
和 alpha
.
该结构类型
的构造函数
接受满足(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?
的值.