->i 什么时候在球拍中真正有用?

When is ->i actually useful in racket?

我一直在经历 Contracts in the Racket Guide

->i 构造允许对函数的 input/output 施加任意约束。

例如,我可以有一个 unzip 函数,它接受一个对列表和 returns 两个列表。使用合同,我可以确认入列表中的每个元素都是一对,并且出列表具有相应的元素。

球拍指南暗示这是合同有用的时候。但似乎这在函数本身内部完成会更好。如果遇到非对,我可能会抛出错误,这将检查列表中的内容。通过正确的函数自动检查输出。

通过比简单类型更复杂的契约以某种方式改进代码的具体示例是什么?

正如您所描述的,几乎所有可以在 ->i 中执行的检查都可以在函数本身中执行,但是 any 检查由大多数情况下,合同可以在职能范围内执行。将信息编码到合同中有一些好处。

  1. 您可以从函数实现中提取不变量。这很好,因为您不需要将函数本身与保护子句混为一谈,您只需编写代码并知道不变量将由合约维护。
  2. 合同努力提供良好的错误报告。他们会自动将 "blame" 分配给违反合同的一方,对于复杂的合同,他们会在错误消息中添加上下文,以尽可能清楚地说明问题所在。

当合约需要在提供给函数的参数中指定依赖项时,这些在 ->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 参数必须接受与序列相同数量的参数,如上所述。此外,它还要求该函数始终 return 是单个值,因为 Racket 函数可以 return 多个值。

在函数体内检查这些不变量要困难得多,因为,尤其是第二个不变量,它们必须延迟到函数本身应用。还必须在每次调用函数时检查它。另一方面,契约包装函数并自动处理。

您是否总是想将函数的每个不变量都编码到合约中?可能不会。但是,如果您想要额外的控制级别,可以使用 ->i