A Clojure Spec that matches and generates an ordered vector of variable length
让我们以
的常规顺序开始
1 2 3 | (require '[clojure.spec :as spec] '[clojure.spec.gen :as gen]) (spec/def ::cat (spec/cat :sym symbol? :str string? :kws (spec/* keyword?))) |
与向量匹配的
1 2 3 4 | (spec/conform ::cat '[af"5"]) => {:sym af, :str"5"} (spec/conform ::cat '[af"5" :key]) => {:sym af, :str"5", :kws [:key]} |
还有列表
1 2 3 4 | (spec/conform ::cat '(af"5")) => {:sym af, :str"5"} (spec/conform ::cat '(af"5" :key)) => {:sym af, :str"5", :kws [:key]} |
如果我们想限制它,我们可以尝试使用
1 2 3 4 5 | (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]]] ...) |
我们也可以尝试使用
1 2 | (spec/def ::and-cat (spec/and vector? (spec/cat :sym symbol? :str string? :kws (spec/* keyword?)))) |
符合条件的
1 2 3 4 5 6 | (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 |
,但遗憾的是未能生成自己的数据,因为
1 2 | (spec/exercise ::and-cat) => Couldn't satisfy such-that predicate after 100 tries. |
所以总结一下:如何编写一个既可以接受又可以生成像
也可以将问题改写为"
独立于规范创建正则表达式模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | (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]) |
要添加Alex的解决方案,请使用以下宏,它定义了矢量猫正则表达式操作:
1 2 3 4 5 6 7 8 9 10 11 | (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))))) |
从
1 2 3 | (spec/def ::solution (let [s (spec/cat :sym symbol? :str string? :kws (spec/* keyword?))] (spec/with-gen s #(gen/fmap vec (spec/gen s))))) |
然后我们可以看到它们产生并接受正确的数据:
1 2 3 4 | (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]}]) |
尽管有一个问题,但规范并未验证输入是否为向量,但它接受诸如list之类的序列:
1 2 | (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]} |