我一直在经历Contracts in the Racket Guide。
->i
构造允许在函数的输入/输出上放置任意约束。
例如,我可以使用unzip
函数来获取对列表并返回两个列表。使用契约,我可以确认列表中的每个元素都是一对,并且外部列表具有相应的元素。
球拍指南暗示这是合同有用的时候。但似乎在函数本身内做得更好。如果遇到非配对,我可能会抛出错误,这会检查列表中的内容。通过具有正确的功能自动检查输出。
通过比简单类型更复杂的契约以某种方式改进代码的具体示例是什么?
答案 0 :(得分:13)
正如您所描述的那样,几乎任何可以在->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
可用。