Clojure spec: a map containing either: s or: height (XOR)

The following clojure spec ::myallows cards that have either a key width: key width or key height: however, this does not allow both of them to be used:

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

Even if he does the job:

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

the code doesn't seem concise to me. Keys are first defined as optional, and then as needed. Does anyone have a more readable solution?

+4
source share
3 answers

clojure.spec aims to stimulate growth-capable specifications. Therefore, it s/keysdoes not support prohibitory keys. It even matches cards with keys that aren't in :reqeither opt.

, :width :height, XOR, OR.

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

- / 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
+3

, , , , , false nil.

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

, int? . , , - nilable boolean, .

:

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

Source: https://habr.com/ru/post/1668028/


All Articles