Welcome! Please see the About page for a little more info on how this works.

0 votes
in Spec by

I have a sequence of characters, and am trying to use (s/cat ...) with (s/or) in order to conform the sequence.

The input could look something like [0 1 2 3] or [\a \b \c \d] and I am trying to use this spec.

(s/conform (s/cat :top (s/or :nums (s/+ #{0 1 2 3 4})
                             :letters (s/+ #{\a \b \c \d})
                  ... more stuff))
           [0 1 2 3])
=> :clojure.spec.alpha/invalid

Not using the (s/or) works if I choose one of :nums or :letters.

(s/conform (s/cat :first-set (s/+ #{0 1 2 3 4}))
           [0 1 2 3])
=> {:first-set [0 1 2 3]}

The initial example works if the entire sequence is wrapped in another vec, but this isn't what I want.

(s/conform (s/cat :first-set (s/or :nums (s/+ #{0 1 2 3 4})
                                   :letters (s/+ #{\a \b \c \d})))
           [[0 1 2 3]])
=> {:first-set [:nums [0 1 2 3]]}

Using (or ...) also seems to only work inside (s/keys). I'm guessing it's because the (or always takes the first spec because it's not falsy.

(s/conform (s/cat :first-set (or (s/+ #{0 1 2 3 4})
                                 (s/+ #{\a \b \c \d})))
           [0 1 2 3])
=> {:first-set [0 1 2 3]}

(s/conform (s/cat :first-set (or (s/+ #{0 1 2 3 4})
                                 (s/+ #{\a \b \c \d})))
           [\a \b \c])
=> :clojure.spec.alpha/invalid

This is what I want to happen.

(s/conform (s/cat :first-set (s/something-here (s/+ #{0 1 2 3 4})
                                               (s/+ #{\a \b \c \d})))
           [0 1 2 3])
=> {:first-set [0 1 2 3]}

(s/conform (s/cat :first-set (s/something-here (s/+ #{0 1 2 3 4})
                                               (s/+ #{\a \b \c \d})))
           [\a \b \c])
=> {:first-set [\a \b \c]}

I'm wondering what to do here.

1 Answer

+3 votes
by
selected by
 
Best answer

You should use s/alt instead of s/or in regex context:

(s/conform (s/cat :top (s/alt :nums (s/+ #{0 1 2 3 4})
                              :letters (s/+ #{\a \b \c \d})))
           [0 1 2 3])
=> {:top [:nums
          [0 1 2 3]]}

Since s/or is not a regex op, it will match the next item instead of a sequence:

(s/conform (s/cat :top (s/or :nums (s/+ #{0 1 2 3 4})
                              :letters (s/+ #{\a \b \c \d})))
           [[0 1 2 3]]) ;; note that it's wrapped vector
=> {:top [:nums
          [0 1 2 3]]}
by
regex ops combine such that they always match a single collection, never a nested collection. The regex ops are cat, alt, *, +, ?, keys*, &.
...