Lets start out with the regular sequence of
(require '[clojure.spec :as spec]
'[clojure.spec.gen :as gen])
(spec/def ::cat (spec/cat :sym symbol? :str string? :kws (spec/* keyword?)))
which matches the vectors
(spec/conform ::cat '[af "5"])
=> {:sym af, :str "5"}
(spec/conform ::cat '[af "5" :key])
=> {:sym af, :str "5", :kws [:key]}
but also the lists
(spec/conform ::cat '(af "5"))
=> {:sym af, :str "5"}
(spec/conform ::cat '(af "5" :key))
=> {:sym af, :str "5", :kws [:key]}
If we want to constrain this we could try using spec/tuple
; but sadly it only matches fixed length vectors, i.e it requires at at the least an empty list to be the last part of the tuple:
(spec/def ::tuple (spec/tuple symbol? string? (spec/* keyword?)))
(spec/conform ::tuple '[af "5"])
=> :clojure.spec/invalid
(spec/exercise ::tuple)
=> ([[r "" ()] [r "" []]] [[kE "" (:M)] [kE "" [:M]]] ...)
We can also try adding on an additional condition to ::cat
with spec/and
:
(spec/def ::and-cat
(spec/and vector? (spec/cat :sym symbol? :str string? :kws (spec/* keyword?))))
which matches fine
(spec/conform ::and-cat '[af "5"])
=> {:sym af, :str "5"}
(spec/conform ::and-cat '[af "5" :key])
=> {:sym af, :str "5", :kws [:key]}
(spec/conform ::and-cat '(af "5" :key))
=> :clojure.spec/invalid
but sadly fails in generating it's own data since the generator for spec/cat
only produces lists which of course won't conform to the vector?
predicate:
(spec/exercise ::and-cat)
=> Couldn't satisfy such-that predicate after 100 tries.
So to summarize: How does one write a spec which is both capable of accepting and generating vectors like [hi "there"]
[my "dear" :friend]
?
One could also rephrase the question as "Is there an alternative to spec/cat
which generates vectors instead of lists?" or "Is it possible to pass a :kind argument to spec/cat
?" or "Can I attach a generator to a spec which takes the output of the original generator and casts it to a vector?".
Create the regex pattern independently from the spec:
(require '[clojure.spec :as s] '[clojure.spec.gen :as gen])
(def pattern
(s/cat :sym symbol? :str string? :kws (s/* keyword?)))
(s/def ::solution
(s/with-gen (s/and vector? pattern)
#(gen/fmap vec (spec/gen pattern))))
(s/valid? ::solution '(af "5" :key)) ;; false
(s/valid? ::solution ['af "5" :key]) ;; true
(gen/sample (s/gen ::solution) 4)
;; ([m ""] [. "" :Q] [- "" :?-/-9y :_7*/!] [O._7l/.?*+ "z" :**Q.tw.!_/+!gN :wGR/K :n/L])
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With