Clojure Spec - spec/and中嵌套spec/or存在问题

5

最近我一直在尝试使用Clojure Spec并遇到了一个意外的错误信息。我发现,如果您在spec/and中嵌套了spec/or,则在spec/or分支之后,spec函数将传递一个已确认的值而不是顶层值。

您可以在这里看到“v”的打印值(虚构的示例):

(spec/valid? (spec/and (spec/or :always-true (constantly true))
                       (fn [v]
                         (println "v:" v)
                         (constantly true)))
         nil)
v: [:always-true nil]
=> true

我认为这可能是从spec/and的文档字符串中有意为之:
接受谓词/spec-forms,例如: (s/and even? #(<% 42)) 返回一个规范化值的规范。连续的规范化值会通过其余的谓词传播。
但是我认为这似乎与直觉相反,因为它会妨碍规范谓词的重用,因为它们需要编写以接受“[ ]”。
如果您有多个spec/or分支,则情况会变得更糟。
(spec/valid? (spec/and (spec/or :always-true (constantly true))
                       (spec/or :also-always-true (constantly true))
                       (fn [v]
                         (println "v:" v)
                       (constantly true)))
         nil)
v: [:also-always-true [:always-true nil]]
=> true

我在这里错过了什么基本的东西吗?
2个回答

8
但是这对我来说似乎不符合直觉,因为这会阻碍规范断言的重复使用。
在我看来,这些行为的替代方案不太吸引人:
- 默认情况下丢弃 `s/or` 的符合标签。如果我们想要,我们总是可以丢弃它,但我们不希望 clojure.spec 代表我们做出决定。Spec 假设我们想知道哪个 `s/or` 分支匹配。 - 不要在 `s/and` 中流动符合值,以换取 spec/predicate 可组合性的代价。
幸运的是,我们可以在必要时丢弃 `s/or` 标签。这里有两个选项:
  • Wrap the s/or in s/noncomforming. Thanks to glts' comment below reminding me about this (undocumented) function!

    (s/valid?
      (s/and
        (s/nonconforming (s/or :s string? :v vector?))
        empty?)
      "")
    => true
    
  • s/and the s/or specs with a s/conformer that discards the tag.

    (s/valid?
      (s/and
        (s/and (s/or :s string? :v vector?)
               ;; discard `s/or` tag here
               (s/conformer second))
        empty?)
      [])
    => true
    

    If you often needed this, you could reduce boilerplate with a macro:

    (defmacro dkdc-or [& key-pred-forms]
      `(s/and (s/or ~@key-pred-forms) (s/conformer second)))
    
如果你有多个spec/or分支,情况会更糟。如果你正在编写数据的规范,并允许使用可替代项(例如s/or、s/alt),并且将有效的可替代项“流入”到后续谓词(s/and)中,我认为将这种知识在后续谓词中更加通用。我很想看到这种类型的spec的更现实的用例,因为可能有更好的方法来定义它。

1
你也可以使用s/nonconforming代替conformer。 - glts
1
Taylor,阅读你的回答总是一种愉悦,因为它们总是“真实”的回答,有理有据,而不是仅仅能解决问题但无助于理解的快速代码。谢谢! - marco.m
感谢花时间撰写了这个很好的答案。我明白您的观点,但是最终我认为很多刚开始编写简单谓词规范的人会遇到问题,因为当您尝试将它们与and/or操作符结合使用时,它们就会停止工作,就像我发现的那样。也许这是需要使用简单 and/or 的情况,这些运算符可以在简单谓词中按预期工作,并提供更高级选项以满足需要的结果的情况? - Dale Thatcher

0
补充说明:问题中发现的Spec行为意味着您可以非常好地拥有(and (s/valid? ::spec-1 v) (s/valid? ::spec2) v),但不是(s/valid? (s/and ::spec-1 ::spec-2) v),由于从::spec-1进行了符合性。
您可能会发现s/and的这种行为令人惊讶(更不用说疯狂了)。但请注意,正如接受的答案所提到的那样,您将拥有(s/valid? (s/and (s/nonconforming ::spec-1) ::spec-2) v)
推论是在使用s/and时,请问自己是否需要s/conforming这条建议可能是s/and docstring的一个有价值的补充。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接