clojure 规范:包含 :with 或 :height (XOR) 的地图

clojure spec: map containing either a :with or a :height (XOR)

以下 clojure 规范 ::my 允许映射具有键 :width 或键 :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

代码对我来说没有那么简洁。首先,键被定义为可选的,然后是必需的。有人对此有更易读的解决方案吗?

clojure.spec 旨在鼓励能够成长的规格。因此它的 s/keys 不支持禁止键。它甚至匹配具有不在 :reqopt 中的键的映射。

但是有一种说法是地图必须至少:width:height,即不是 XOR,只是 OR.

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

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

(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

只是想对原始问题中的规范进行小的修改,如果任何键持有的值是假的,即 falsenil

(spec/valid? ::my {:width nil})
=> false

当然,如果 int? 对问题中的值施加给定约束,则不会发生这种情况。但也许后代有人允许他们的价值观是 nilable 或布尔值,在这种情况下这个答案变得很方便。

如果我们改为将规范定义为:

(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)))

我们得到的结果是

(spec/valid? ::my {:width nil})
=> true
(spec/valid? ::my {:width nil :height 5})
=> false