4

我最近一直在尝试 Clojure 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

我认为这可能是从规范/和文档字符串故意的:

采用谓词/规范形式,例如

(s/甚至?#(< % 42))

返回一个规范,该规范返回符合的值。连续一致的值通过其余谓词传播。

但这对我来说似乎违反直觉,因为它会妨碍规范谓词的重用,因为需要编写它们以接受“[<or branch> <actual value>]”。

如果您有多个规范/或分支,情况会变得更糟:

(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

我在这里错过了一些基本的东西吗?

4

1 回答 1

7

但这对我来说似乎违反直觉,因为它会妨碍规范谓词的重用

IMO 这些行为的替代方案不那么吸引人:

  • 默认情况下丢弃s/or符合标准的标签。如果我们愿意,我们总是可以丢弃它,但我们不希望 clojure.spec 为我们做出决定。Spec 假设我们想知道哪个分支匹配。s/or
  • 不要s/and以牺牲规范/谓词的可组合性为代价将符合的值流入。

幸运的是,s/or如有必要,我们可以丢弃标签。这里有两个选项:

  • s/or进去s/noncomforming。感谢下面 glts 的评论提醒我这个(未记录的)功能!

    (s/valid?
      (s/and
        (s/nonconforming (s/or :s string? :v vector?))
        empty?)
      "")
    => true
    
  • s/ands/or带有s/conformer丢弃标签的规范。

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

    如果你经常需要这个,你可以使用宏来减少样板:

    (defmacro dkdc-or [& key-pred-forms]
      `(s/and (s/or ~@key-pred-forms) (s/conformer second)))
    

如果您有多个规范/或分支,情况会变得更糟

如果您正在为允许替代(例如s/or, )的数据编写规范s/alt,并且您正在将有效的替代“流动”到后续谓词(s/and)中,IMO 在后续谓词中拥有该知识更普遍有用。我有兴趣看到这种规范的更现实的用例,因为可能有更好的方法来规范它。

于 2018-06-24T11:18:31.357 回答