2

我希望在生成器函数的前后条件中使用规范。下面描述了我希望做的一个简化示例:

(defn positive-numbers
  ([]
   {:post [(s/valid? (s/+ int?) %)]}
   (positive-numbers 1))
  ([n]
   {:post [(s/valid? (s/+ int?) %)]}
   (lazy-seq (cons n (positive-numbers (inc n))))))

(->> (positive-numbers) (take 5))

但是,像这样定义生成器函数似乎会导致堆栈溢出,原因是规范会急切地尝试评估整个事情,或者类似的事情......

是否有另一种使用规范来描述生成器函数的:post结果的方法,例如上面的生成器函数(不会导致堆栈溢出)?

4

1 回答 1

2

理论上正确的答案是,通常您无法在没有意识到所有情况下检查惰性序列是否与规范匹配。

在您的具体示例中(s/+ int?),给定一个惰性序列,如何仅通过观察序列来确定其所有元素是否都是整数?无论您检查多少元素,下一个元素始终可能是关键字。

这是诸如core.typed之类的类型系统可能能够证明的事情,但基于运行时谓词的断言将无法检查。

现在,除了s/+and之外s/*,规范(从 Clojure 1.9.0-alpha14 开始)还有一个名为 的组合子s/every,其文档字符串如下所示:

请注意,'every' 不会进行详尽的检查,而是对 *coll-check-limit* 元素进行采样。

所以我们有例如

(s/valid? (s/* int?) (concat (range 1000) [:foo]))
;= false

(s/valid? (s/every int?) (concat (range 1000) [:foo]))
;= true

(默认*coll-check-limit*值为101)。

这实际上不是对您的示例的直接修复 - 插入s/every代替s/+不起作用,因为每个递归调用都希望验证自己的返回值,这将涉及实现更多的序列,这将涉及更多的递归调用等等。但是您可以将序列构建逻辑分解为没有后置条件的辅助函数,然后positive-numbers声明后置条件并调用该辅助函数:

(defn positive-numbers* [n]
  (lazy-seq (cons n (positive-numbers* (inc n)))))

(defn positive-numbers [n]
  {:post [(s/valid? (s/every int? :min-count 1) %)]}
  (positive-numbers* n))

注意注意事项:

  1. 这仍然会实现您序列的很大一部分,这可能会对您的应用程序的性能配置文件造成严重破坏;

  2. 这里唯一的无懈可击的保证是实际检查的前缀是所需的,如果 seq 在位置 123456 有一个奇怪的项目,那将被忽视。

由于 (1),这作为仅测试断言更有意义。(2) 可能是可以接受的——你仍然会发现一些愚蠢的错别字,而且规范的文档价值仍然存在;如果不是,并且您确实希望绝对无懈可击地保证您的返回类型符合要求,那么 core.typed (可能仅在本地用于少数命名空间)可能是更好的选择。

于 2017-02-05T00:24:47.143 回答