【问题标题】:how to spec a lazy-seq generating function?如何指定一个惰性序列生成函数?
【发布时间】:2017-02-04 23:40:41
【问题描述】:

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

(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 结果(不会导致堆栈溢出)?

【问题讨论】:

    标签: clojure clojure.spec


    【解决方案1】:

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

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

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

    现在,除了s/+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(可能仅在本地用于少数命名空间)可能是更好的选择。

    【讨论】:

    • 非常感谢您的详尽回答。我认为应该使用:gen-max 而不是:min-count。我注意到的另一件事是,这些带有规范的前后条件是令人难以置信的惩罚 wrt 性能。 - 所以我可能会创建fdef 规范。我对 clojure 很陌生,并且来自 F#/Haskell/Idris 等类型检查语言,我真的很怀念类型检查器提供的保证,最重要的是类型提供的文档价值。 - 只要时间允许,我一定会仔细查看 core.typed。
    • :gen-max 用于控制生成(如s/exerciseclojure.spec.test/check 所采用)并且对满足规范的数据没有影响。 :min-count 是规范的一部分——它对可以被视为满足规范的集合的大小施加了一个附加条件。我把它放进去是因为你在问题中使用了s/+ 而不是s/*。同意重新:规范断言正在惩罚 w.r.t.表现。出于这个原因,它们应该只在测试/开发时使用。还同意fdef,这绝对是在此处附加规范的最佳方法。
    猜你喜欢
    • 2011-03-23
    • 1970-01-01
    • 1970-01-01
    • 2022-12-07
    • 1970-01-01
    • 2013-11-28
    • 1970-01-01
    • 1970-01-01
    • 2021-02-13
    相关资源
    最近更新 更多