数学函数有一个领域和范围。领域代表能够接受的参数,范围代表产生的值。传统表示方法如下
f:A->B
程序语言的函数也有领域和范围,契约能保证函数值接受领域的值和只产生范围的值。->创建一个函数契约。
#lang racket
(provide (contract-out
[deposit (-> number? any)]
[balance (-> number?)]))
(define amout 0)
(define (deposit a) (set! amount (+ amount a)))
(define (balance) amout)
这个模块导出了两个函数
- deposit 接受一个数字返回某个值
- balance 返回一个数字
但一个模块导出一个函数,它建立了两个交流通道。它自己作为服务器,导入这个函数的模块则作为客户端。如果客户端调用一个函数,它发送一个值到服务器。相反的,如果这个函数调用结束函数返回一个值,服务器发送一个值给客户端。客户端和服务器的区别很重要,因为当出现错误,会把错误归咎到某个部分。
->本身并不是契约,它是一个契约联接符。
7.2.1->样式
如果你使用数学函数,你可能更愿意让契约箭头出现在领域和范围之间,而不是在开头。事实上,你确实可以这么做
(provide (contract-out
[deposit (number? . -> . any)]))
在racket里面一个s表达式如果含有两个点的符号在中间,读取器则会重排s表达式让符号在前面。所以它等价于
(-> number? any)
7.2.2使用 define/contract 和 ->
define/contract形式也能使用契约来定义。
(define/contract (deposit amount)
(->number? any)
; implementation goes here
...)
这种使用方式有两个影响
1 因为调用的时候总是检查契约,即使在模块内部。所以,它会造成性能下降。特别是在循环和递归的时候。
2 在某些情况下,同模块调用时,可能有一些更加宽松的输入限制。在这种情况下,define/contract建立的限制太严格了。
7.2.3any和any/c
any契约匹配任何一种结果,它只能使用在范围的位置。除此之外,我们能使用void?契约,表示函数会返回(void)值。void?契约会被契约检测系统检查,当函数返回值的时候。相反的,any契约告诉契约检测系统不用检查返回值,它告诉客户端模块服务器不会对函数返回值作出任何承诺,可以单个值也可以多值。
any/c和any类似,但是,any/c代表一个单值。
(define (f x) (values (+ x 1) (- x 1)))
上面这个函数匹配(-> integer? any),但是不匹配(-> integer? any/c)。当承诺返回单个结果很重要的时候使用any/c,当竟可能少的承诺返回结果时使用any。
7.2.4定制自己的契约
#lang racket
(define (amount? a)
(and (number? a) (integer? a) (exact? a) (>= a 0)))
(provide (contract-out
; an amount is a natural number of cents
; is the given number an amount?
[deposit (-> amount? any)]
[amount? (-> any/c boolean?)]
[balance (-> amount?)]))
(define amount 0)
(define (deposit a) (set! amount (+ amount a)))
(define (balance) amount)
上面这个模块自定义了amount?函数作为契约。如果客户端无法理解amount?,那就没有任何意义。所以模块也导出了amount?谓词本身。我们可使用natural-number/c替代amount?,因为它们的约束恰好相同。
(provide (contract-out
[depsoit (-> natural-number/c any)]
[balance (-> natural-number/c)]))
每个接受一个参数的函数都能被当做谓词在契约里使用。所以契约组合器变的很有用。比如and/c和or/c.
(define amount/c
(and/c number? integer? exact? (or/c positive? zero?)))
(provide (contract-out
[deposit (-> amount/c any)]
[balance (-> amount/c)]))
7.2.5 高阶函数契约
函数契约不是只能使用简单的谓词在他们的领域和范围。任何这里讨论的组合器,包括函数契约本身,都能被当做契约来使用在函数的参数和结果上。
(->integer? (-> integer? integer?))
它表示接受一个参数并返回一个函数,这个函数接受一个参数并最终返回一个结果。
类似的还有
(-> (->ineger? integer?) integer?)
7.2.6 契约消息tempN
如果在契约里传入一个lambda表达式(匿名函数),则调用的时候错误信息会显示tempN,如果想改进这种错误提示,可以先申明一个有函数名的谓词判断函数,那么提示就会变得清晰。
7.2.7解释契约错误信息
一般来说,契约的错误信息包含下面留个方面
- 一个和约束相关的方法或函数,并有短语
contract violation
或者broken its contract
。这个短语取决于客户端还是服务器违反了契约。
deposit: contract violation - 一个描述希望值和实际值
expected: amount
given:-10 - 完整的契约加上那个被违反了
in: the ist argument of
(-> amount any) - 契约所属的模块
contract from: improved-bank-server - 归咎于谁
blaming:top-level
(assuming the contract is correct) - 源代码出现的位置
at: eval:5.0