Fork me on GitHub
#clojure-spec
<
2020-06-14
>
misha22:06:20

is there a sensible way to specify min/max items count for s/? s/* other than function predicate, like?:

(s/and ::my-cat-spec #(< 5 (count (s/unform ::my-cat-spec %))))

sgepigon22:06:54

@misha If you’re using regex specs, you probably want s/& instead of s/and

sgepigon22:06:57

so (s/& ::my-cat-spec #(< 5 (count %)))

misha22:06:24

you are probably right about s/&, but I still will have to s/unform

misha22:06:18

actually, since I apply custom pred to the top spec, it does not seem to matter whether I use s/and or s/&

misha22:06:12

the reason I hope there is another way, is because I'd like to avoid unform

sgepigon22:06:23

Hmm I guess I’m still unsure why you need s/unform. Perhaps there’s something about ::my-cat-spec I’m missing:

$ clj
Clojure 1.10.1
user=> (require '[clojure.spec.alpha :as s])
nil
user=> (s/def ::points (s/+ int?))
:user/points
user=> (s/conform (s/& ::points #(< 5 (count %))) [0 1 2 3 4 5])
[0 1 2 3 4 5]

misha22:06:56

(s/def :foo/bar (s/cat :1 (s/+ (s/or :a int?))))
(s/explain 
    (s/& :foo/bar #(->> % (s/unform :foo/bar) count (= 3)))
    [1 2 3])

;Success!
=> nil
(s/explain 
    (s/& :foo/bar #(->> % count (= 3)))
    [1 2 3])

;{:1 [[:a 1] [:a 2] [:a 3]]} - failed: (->> % count (= 3))
=> nil

misha23:06:40

in your example conformed int it just int, so unforming is identity. but if you have some branchy spec (s/or, s/alt, s/cat) - it will change the shape of the data, so in custom predicates for s/and and s/& you either have to unform, or validate conformed value (but this requires you knowing exactly what kind of spec is as the first arg to s/and s/&)

misha23:06:19

for the homogeneous collections I can use s/coll and it has :max-count and :min-count options. I was hoping s/cat has something similar

sgepigon23:06:46

I see. Thanks for providing an example.

sgepigon23:06:02

I believe in spec2 this is “non-flowing s/and

misha23:06:28

yeah, should have mentioned: need this for spec1 opieop

sgepigon23:06:55

You can modify the latter one to go into the map and count from there if you really want to avoid s/unform

sgepigon23:06:57

(s/explain 
 (s/& :foo/bar #(->> % :1 count (= 3)))
 [1 2 3])

sgepigon23:06:19

because you end up putting all the conformed values under the :1 key.

misha23:06:54

what about

(s/cat :1 (s/+ (s/or :a int?))) :2 string?)
kappa

misha23:06:27

the thing is: this is for translating json-schema to clojure spec, so all specs are dynamic, and I don't do this manually. So I need to find the most sane lowest common denominator

ikitommi06:06:08

interesting. any code to share?

Aron08:06:10

second that. I will have to do something similar soonish, and I am not even sure where I will begin : )

misha09:06:15

within this week, I hope

sgepigon23:06:56

(defn conformed-count
  [conformed]
  (->> conformed
       vals
       (reduce (fn [acc x]
                 (if (coll? x)
                   (+ acc (count x))
                   (inc acc)))
               0)))

(s/conform
 (s/& :foo/bar #(= 4 (conformed-count %)))
           [1 2 3 "blah"])

🤷 3
misha23:06:04

but what if it is just int? instead of s/cat ? troll

sgepigon23:06:26

Yeah I don’t think there’s a great solution for this now in spec1.

sgepigon23:06:20

s/unform might be your best bet.

misha23:06:37

so far, it seems very much like it, yes