clojure spec:包含a:with或a:height(XOR)

时间:2017-01-27 17:16:12

标签: clojure clojure.spec

以下clojure规范::my允许使用密钥:width或key:height的地图,但不允许同时拥有这两个:

(s/def ::width int?)

(s/def ::height int?)

(defn one-of-both? [a b]
  (or (and a (not b))
      (and b (not a))))

(s/def ::my (s/and (s/keys :opt-un [::width ::height])
                   #(one-of-both? (% :width) (% :height))))

即使它完成了这项工作:

(s/valid? ::my {})
false
(s/valid? ::my {:width 5})
true
(s/valid? ::my {:height 2})
true
(s/valid? ::my {:width 5 :height 2})
false

代码看起来并不简洁。首先,将键定义为可选,然后根据需要定义。有没有人对此有更可读的解决方案?

3 个答案:

答案 0 :(得分:7)

clojure.spec旨在鼓励能够增长的规格。因此它的s/keys不支持禁止密钥。它甚至匹配具有既不在:req也不在opt的密钥的地图。

但是有一种方法可以说地图必须至少拥有:width :height,即不是XOR,只是OR。< / p>

(s/def ::my (s/keys :req-un [(or ::width ::height)]))

答案 1 :(得分:4)

此功能内置于规范中 - 您可以在req-un中指定和/或模式:

(s/def ::my (s/keys :req-un [(or ::width ::height)]))
:user/my
user=> (s/valid? ::my {})
false
user=> (s/valid? ::my {:width 5})
true
user=> (s/valid? ::my {:height 2})
true
user=> (s/valid? ::my {:width 5 :height 2})
true

答案 2 :(得分:0)

只是想在原始问题中对规范进行一些小修改,如果任何键所持有的值都是假的,即逻辑将失败,即//String line = inputStream.next(); false

nil

当然,(spec/valid? ::my {:width nil}) => false 给定问题中的值的约束不会发生这种情况。但也许后人中的某些人允许他们的值为nilable或boolean,在这种情况下,这个答案会变得很方便。

如果我们将规范定义为:

int?

我们得到

的结果
(defn xor? [coll a-key b-key]
  (let [a (contains? coll a-key)
        b (contains? coll b-key)]
    (or (and a (not b))
        (and b (not a)))))

(spec/def ::my (spec/and (spec/keys :opt-un [::width ::height])
                         #(xor? % :width :height)))