On this page:
7.1.1 Contract Violations
7.1.2 Experimenting with Contracts and Modules
7.1.3 Experimenting with Nested Contract Boundaries

7.1 Contracts and Boundaries🔗

如同两个商业伙伴之间的合同,软件 contract 是双方之间的协议。该协议为从一方传递到另一方的每个"产品"(或值)指定了义务和保证。

contract 由此在双方之间建立了一个边界。每当一个值跨越此边界时,contract 监控系统就会执行 contract 检查,确保双方遵守已建立的 contract。

基于这一理念,Racket 主要在 module 边界处鼓励使用 contract。具体而言,程序员可以将 contract 附加到 provide 子句上,从而对导出值的使用施加约束和承诺。例如,导出声明
#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 中有解释。