7.1 Contracts and Boundaries
如同两个商业伙伴之间的合同,软件 contract 是双方之间的协议。该协议为从一方传递到另一方的每个"产品"(或值)指定了义务和保证。
contract 由此在双方之间建立了一个边界。每当一个值跨越此边界时,contract 监控系统就会执行 contract 检查,确保双方遵守已建立的 contract。
#lang racket (provide (contract-out [amount positive?])) (define amount ...)
向上述 module 的所有使用者承诺 amount 的值将始终是一个正数。contract 系统会仔细监控 module 的义务。每次使用者引用 amount 时,监控器都会检查 amount 的值是否确实是一个正数。
contract 库已内置于 Racket 语言中,但如果你希望使用 racket/base,可以显式地 require contract 库,如下所示:
#lang racket/base (require racket/contract) ; now we can write contracts (provide (contract-out [amount positive?])) (define amount ...)
7.1.1 Contract Violations
如果我们将 amount 绑定到一个非正数,
#lang racket (provide (contract-out [amount positive?])) (define amount 0)
那么,当 module 被 require 时,监控系统会发出 contract violation 信号,并 blame 该 module 违背了其承诺。
一个更严重的错误是将 amount 绑定到一个非数字值:
#lang racket (provide (contract-out [amount positive?])) (define amount 'amount)
在这种情况下,监控系统会将 positive? 应用于一个 symbol,但 positive? 会报告错误,因为它的定义域仅限于数字。为了让 contract 在所有 Racket 值上都能捕获我们的意图,我们可以使用 and/c 将两个 contract 组合起来,确保值既是一个数字又是正数:
(provide (contract-out [amount (and/c number? positive?)]))
7.1.2 Experimenting with Contracts and Modules
本章中的所有 contract 和 module(紧随其后的除外)均使用描述 module 的标准 #lang 语法编写。由于 module 作为 contract 中双方的边界,示例涉及多个 module。
要在单个 module 或 DrRacket 的 definitions area 中实验多个 module,请使用 Racket 的子 module。例如,可以这样尝试本节前面的示例:
#lang racket (module+ server (provide (contract-out [amount (and/c number? positive?)])) (define amount 150)) (module+ main (require (submod ".." server)) (+ amount 10))
每个 module 及其 contract 都被括号包裹,前面是 module+ 关键字。module 之后的第一个形式是 module 的名称,用于后续的 require 语句中(通过 require 引用时,名称前会加上 ".." 前缀)。
7.1.3 Experimenting with Nested Contract Boundaries
在许多情况下,在 module 边界处附加 contract 是合理的。然而,能够以比 module 更细粒度地使用 contract 通常更为方便。define/contract 形式支持这种用法:
#lang racket (define/contract amount (and/c number? positive?) 150) (+ amount 10)
在此示例中,define/contract 形式在 amount 的定义与其周围上下文之间建立了一个 contract 边界。换句话说,这里的双方是定义本身和包含它的 module。
创建这些 嵌套 contract 边界 的形式有时使用起来可能很微妙,因为它们可能产生意外的性能影响,或者 blame 一个看似不太直观的一方。这些微妙之处在 Using define/contract and -> 和 Contract 边界与 define/contract 中有解释。