Clojure Spec匹配并生成可变长度的有序向量

让我们从常规序列开始

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

它与矢量相匹配

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

还有清单

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

如果我们想要约束这个,我们可以尝试使用spec / 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]]] ...)

我们还可以尝试使用spec /和为cat添加额外的条件:

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

哪个匹配好

(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

但遗憾的是无法生成它自己的数据,因为spec / cat的生成器只生成列表,当然这些列表不符合向量?谓语:

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

总结一下:如何编写一个能够接受和生成矢量的规范,如[hi“there”] [我的“亲爱的”:朋友]?

人们还可以将这个问题重新定义为“是否有替代spec / cat而生成向量而不是列表?”或者“是否有可能通过一个:善意的说法/规范/猫?”或者“我可以将生成器附加到获取原始生成器的输出并将其转换为向量的规范吗?”.

最佳答案 独立于规范创建正则表达式模式:

(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])
点赞