如何始终为规范中的可选键生成数据?

How to always generate data for optional keys in a spec?

如果我有这样的规范

(clojure.spec/def ::person (clojure.spec/keys :req [::name ::address] :opt [::age]))

当我这样做时

(clojure.spec.gen/generate (clojure.spec/gen ::person))

有没有办法告诉生成器在为其生成数据时始终考虑可选键?

我知道这可以通过自定义生成器来完成,但我想知道是否已经有可用的功能,或者可能有更简单的方法,不需要我定义自定义生成器。

我认为对你的问题的简短回答是 "no" 但你可以 s/merge 你的规范需要可选键:

(s/def ::name string?)
(s/def ::age pos-int?)
(s/def ::person (s/keys :req [::name] :opt [::age]))

(gen/sample (s/gen ::person)) ;; ::age not always gen'd
(gen/sample                   ;; ::age always gen'd
  (s/gen (s/merge ::person (s/keys :req [::age]))))

您可以编写一个宏来生成执行此操作的 s/keys 规范 w/generator。

我的方法是遍历该规范的形式(使用 clojure.spec.alpha/form),如果规范是使用 clojure.spec.alpha/keys 创建的,则将可选键合并到必需键中,最后重新生成规范。

(defn merge-opt-keys
  "Merges optional keys into requried keys (for specs which are created using `clojure.spec.alpha/keys`) using a spec's form/description"
  [fspec]
  (let [keymap (into {} (map (fn [pair] (vec pair)) (partition 2 (rest fspec))))]
    (->> (cond-> {}
           (contains? keymap :opt)
             (assoc :req (vec (concat (keymap :req) (keymap :opt))))
           (contains? keymap :opt-un)
             (assoc :req-un (vec (concat (keymap :req-un) (keymap :opt-un)))))
         (mapcat identity)
         (cons 'clojure.spec.alpha/keys))))

(clojure.spec.alpha/def ::name string?)
(clojure.spec.alpha/def ::desc string?)
(clojure.spec.alpha/def ::book (clojure.spec.alpha/keys :req [::name] :opt [:desc]))

(clojure.spec.gen.alpha/generate (clojure.spec.alpha/gen (eval (merge-opt-keys (clojure.spec.alpha/form ::book)))))