Clojure 如何始终为等级库中的可选键生成数据?

Clojure 如何始终为等级库中的可选键生成数据?,clojure,clojure.spec,test.check,Clojure,Clojure.spec,Test.check,如果我有一个像 (clojure.spec/def ::person (clojure.spec/keys :req [::name ::address] :opt [::age])) 当我这么做的时候 (clojure.spec.gen/generate (clojure.spec/gen ::person)) 有没有办法告诉生成器在生成数据时总是考虑可选密钥? 我知道这可以通过自定义生成器来实现,但我想知道是否有一种功能已经可用,或者是一种更简单的方法,它不需要我定义自定义生成器。我想你

如果我有一个像

(clojure.spec/def ::person (clojure.spec/keys :req [::name ::address] :opt [::age]))
当我这么做的时候

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

有没有办法告诉生成器在生成数据时总是考虑可选密钥?


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

我想你的问题的简短答案是“否”但是您可以
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
spec,其中包含一个生成器来执行此操作。

我认为您的问题的简短答案是“否”,但您可以
s/merge
将您的spec与需要可选键的spec合并:

(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
spec 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)))))

我的方法是遍历该规范的表单(使用
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)))))

是否希望所有生成的值都具有所有可选键?您的用例是什么?如果有一个标志的话,我会很惊讶。是的,无论生成什么值,都应该有所有键(包括可选键)。如果所有生成的值都应该包含可选键,那么,然后这些键不是可选的:-),或者用另一种方式说:它们不会测试处理不存在可选键的代码路径。是否希望所有生成的值都具有所有可选键?您的用例是什么?如果有这样的标志,我会感到惊讶。是的,无论生成什么值,都应该有所有键(包括可选键)。如果所有生成的值都应该包含可选键,那么这些键就不是可选键:-),或者用另一种方式说:它们不会测试处理不存在可选键的代码路径。