clojure-spec

About: http://clojure.org/about/spec Guide: http://clojure.org/guides/spec API: https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html
bhaim123 2020-08-19T14:24:08.170400Z

Hi, Would appreciate some help 🙂 What am I doing wrong? I have a spec for an empty map: `

(s/def ::empty_map (s/and empty? map?))
and then I build: `
(s/def ::name string?)
(s/def ::someone1 string?)
(s/def ::someone2 string?)
(s/def ::someone_big (s/or
                       ::empty_map
                       (s/keys :req-un [::someone1 ::someone2])))
(s/def ::final (s/keys :req-un [::name ::someone_big]))
The idea here that I would be able to pass:
{:name "abc" :someone_big {}}
And it wold pass, since it is an empty map. But what I get is an error on the required params:
(s/explain ::final {:name "abc" :someone_big {}})
{} - failed: (contains? % :someone1) in: [:someone_big] at: [:someone_big :<NS>empty_map] spec: :<NS>/someone_big
{} - failed: (contains? % :someone2) in: [:someone_big] at: [:someone_big :<NS>/empty_map] spec: :<NS>/someone_big
Any help would be appreciated 🙂

sgepigon 2020-08-19T14:48:23.170900Z

@bhaim123 You need to label each branch of the s/or e.g.

(s/def ::someone_big (s/or :empty-map ::empty_map
                             :someone (s/keys :req-un [::someone1 ::someone2])))
user=> (s/conform ::final {:name "abc" :someone_big {}})
{:name "abc", :someone_big [:empty-map {}]}

bhaim123 2020-08-19T15:14:59.171800Z

Thanks, but I don’t want to send the keyword empty map, the someone_big should be either an empty map. or a map of 2 keys

sgepigon 2020-08-19T15:25:32.172Z

I’m not sure I follow. The spec does capture that it’s either an empty map or a map of two keys. You’re not “sending” any keywords, you’re labelling alternatives. Both (s/conform ::final :empty-map) and (s/conform ::final :someone) are invalid for this spec.

sgepigon 2020-08-19T15:26:35.172200Z

The original ::someone_big spec you posted was a s/or with 1 branch: it could only be a map with two keys and it was labelled ::empty_map. If you reversed the order it might be clearer why it’s wrong:

(s/def ::someone_big (s/or
                       (s/keys :req-un [::someone1 ::someone2])
                       ::empty_map))
Unexpected error (AssertionError) macroexpanding s/or at (REPL:1:22).
Assert failed: spec/or expects k1 p1 k2 p2..., where ks are keywords
(c/and (even? (count key-pred-forms)) (every? keyword? keys))

sgepigon 2020-08-19T15:27:05.172400Z

Notice the path from the s/explain you posted:

at: [:someone_big :<NS>empty_map]

alexmiller 2020-08-19T15:37:53.173500Z

as an aside, (s/def ::empty_map (s/and map? empty?)) would be better than what you have as it should gen when the other would not

đź‘Ť 3
bhaim123 2020-08-19T18:33:02.173900Z

Thank you very much @sgepigon

đź‘Ť 1