5

让我们从常规序列开始

(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]]] ...)

我们也可以尝试在::catwith 中添加一个附加条件spec/and

(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只生成当然不符合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 回答 3

2

独立于规范创建正则表达式模式:

(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])
于 2017-04-05T14:15:32.510 回答
0

原来没有解决这个问题的简单方法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]}
于 2017-04-05T13:59:04.140 回答
0

要添加 Alex 的解决方案,这里有一个宏,它定义了 vector-cat 正则表达式操作:

(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)))))
于 2017-04-07T10:45:44.660 回答