A Clojure Spec that matches and generates an ordered variable-length vector

Let's start with a regular sequence

(require '[clojure.spec     :as spec]
         '[clojure.spec.gen :as gen])
(spec/def ::cat (spec/cat :sym symbol? :str string? :kws (spec/* keyword?)))

which corresponds to vectors

(spec/conform ::cat '[af "5"])
=> {:sym af, :str "5"}
(spec/conform ::cat '[af "5" :key])
=> {:sym af, :str "5", :kws [:key]}

but also 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 limit this, we can try to use spec/tuple; but, unfortunately, it corresponds only to vectors of a fixed length, i.e. so that at least the empty list is 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 to add an additional condition to ::catwith spec/and:

(spec/def ::and-cat
  (spec/and vector? (spec/cat :sym symbol? :str string? :kws (spec/* keyword?))))

which corresponds to a 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, unfortunately, it is not possible to create your own data, because the generator for spec/catcreates only lists, which, of course, will not correspond to the predicate vector?:

(spec/exercise ::and-cat)
=> Couldn't satisfy such-that predicate after 100 tries.

, : , [hi "there"] [my "dear" :friend]?

" spec/cat, ?" " : kind spec/cat?"? " , ?".

+4
3

:

(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])
+2

clojure-1.9.0-alpha15. , cat, :

(spec/def ::solution
  (let [s (spec/cat :sym symbol? :str string? :kws (spec/* keyword?))]
    (spec/with-gen s #(gen/fmap vec (spec/gen s)))))

, :

(spec/exercise ::solution)
=> ([[T ""] {:sym T, :str ""}]
    [[t* "Z" :g*] {:sym t*, :str "Z", :kws [:g*]}]
    [[G?8 "td" :*K/j] {:sym G?8, :str "td", :kws [:*K/j]}])

, , , , :

(spec/conform ::solution '(N-G.?8?4/- "" :G7y_.?Gx_/Oy1Dv :g!/Ooh0 :N-??h/o+cN))
=> {:sym N-G.?8?4/-, :str "", :kws [:G7y_.?Gx_/Oy1Dv :g!/Ooh0 :N-??h/o+cN]}
0

To add Alex's solution, here is a macro that defines the vector-cat regex operation:

(defmacro vcat
  "Takes key+pred pairs, e.g.

  (vcat :e even? :o odd?)

  Returns a regex op that matches vectors, returning a map containing
  the keys of each pred and the corresponding value. The attached
  generator produces vectors."
  [& key-pred-forms]
  `(spec/with-gen (spec/and vector? (spec/cat ~@key-pred-forms))
     #(gen/fmap vec (spec/gen (spec/cat ~@key-pred-forms)))))
0
source

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


All Articles