正如您所描述的,几乎所有可以在函数中执行的检查都可以->i
在函数本身内执行,但话又说回来,合约执行的任何检查在大多数情况下都可以在函数本身内执行。将信息编码到合同中提供了一些优势。
- 您可以从函数实现中提取不变量。这很好,因为您不需要将函数本身与保护条款混淆,您只需编写代码并知道不变量将由合约维护。
- 合同努力提供良好的错误报告。他们会自动将“责任”分配给违反合同的一方,对于复杂的合同,他们会在错误消息中添加上下文,以尽可能清楚地说明问题所在。
->i
当合约需要在提供给函数的参数中指定依赖关系时,这些最为明显。例如,我有一个集合库,其中包含一个subsequence
函数。它需要三个参数,一个序列、一个开始索引和一个结束索引。这是我用来保护它的合同:
(->i ([seq sequence?]
[start exact-nonnegative-integer?]
[end (start) (and/c exact-nonnegative-integer? (>=/c start))])
[result sequence?])
这允许我明确指定结束索引必须大于或等于开始索引,并且我不必担心在我的函数中检查该不变量。当我违反此合同时,我会收到一条很好的错误消息:
> (subsequence '() 2 1)
subsequence: contract violation
expected: (and/c exact-nonnegative-integer? (>=/c 2))
given: 1
which isn't: (>=/c 2)
它也可以用来确保更复杂的不变量。我还定义了自己的map
函数,与 Racket 的内置函数一样,它map
支持可变数量的参数。提供给的过程map
必须接受与提供的序列相同数量的参数。我使用以下合同map
:
(->i ([proc (seqs) (and/c (procedure-arity-includes/c (length seqs))
(unconstrained-domain-> any/c))])
#:rest [seqs (non-empty-listof sequence?)]
[result sequence?])
这份合同确保了两件事。首先,proc
参数必须接受与序列相同数量的参数,如上所述。此外,它还要求该函数始终返回单个值,因为 Racket 函数可以返回多个值。
在函数体内检查这些不变量将更加困难,因为,特别是对于第二个不变量,它们必须延迟到函数本身被应用。还必须在每次调用该函数时对其进行检查。另一方面,合约包装函数并自动处理它。
您是否总是想将函数的每个不变量都编码到合约中?可能不是。但是,如果您想要额外的控制级别,->i
则可以使用。