Clojure 规范的正确嵌套?

Proper nesting of Clojure Specs?

我想我可能对函数中嵌套规范的正确顺序有疑问 - 特别是 s/with-gens/or...

我有这个功能和规格:

(defn set-gift-pair-in-gift-history [g-hist g-year g-pair]
  (if (nil? g-hist)
    [{:giver :none, :givee :none}]
    (assoc g-hist g-year g-pair)))
(s/fdef set-gift-pair-in-gift-history
        :args (s/with-gen
                (s/or :input-hist (s/and
                                    (s/cat :g-hist :unq/gift-history
                                           :g-year (s/and int? #(> % -1))
                                           :g-pair :unq/gift-pair)
                                    #(<= (:g-year %) (count (:g-hist %))))
                      :input-nil (s/and
                                   (s/cat :g-hist nil?
                                          :g-year (s/and int? #(> % -1))
                                          :g-pair :unq/gift-pair)
                                   #(<= (:g-year %) (count (:g-hist %)))))
                #(gen/let [hist (s/gen :unq/gift-history)
                           year (gen/large-integer* {:min 0 :max (max 0 (dec (count hist)))})
                           pair (s/gen :unq/gift-pair)]
                   [hist year pair]))
        :ret :unq/gift-history)

哪个测试正确:

(stest/check `set-gift-pair-in-gift-history)
=>
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451
                0x729d93b6
                "clojure.spec.alpha$fspec_impl$reify__2451@729d93b6"],
  :clojure.spec.test.check/ret {:result true,
                                :num-tests 1000,
                                :seed 1531413555637},
  :sym clojure-redpoint.roster/set-gift-pair-in-gift-history})

其参数正确符合:

(s/conform (s/or :input-hist (s/and
                               (s/cat :g-hist :unq/gift-history
                                      :g-year (s/and int? #(> % -1))
                                      :g-pair :unq/gift-pair)
                               #(<= (:g-year %) (count (:g-hist %))))
                 :input-nil (s/and
                              (s/cat :g-hist nil?
                                     :g-year (s/and int? #(> % -1))
                                     :g-pair :unq/gift-pair)
                              #(<= (:g-year %) (count (:g-hist %)))))
           [[{:giver :GeoHar, :givee :JohLen}] 0 {:giver :RinStaXX, :givee :PauMccXX}])
=>
[:input-hist
 {:g-hist [{:giver :GeoHar, :givee :JohLen}],
  :g-year 0,
  :g-pair {:giver :RinStaXX, :givee :PauMccXX}}]

(s/conform (s/or :input-hist (s/and
                               (s/cat :g-hist :unq/gift-history
                                      :g-year (s/and int? #(> % -1))
                                      :g-pair :unq/gift-pair)
                               #(<= (:g-year %) (count (:g-hist %))))
                 :input-nil (s/and
                              (s/cat :g-hist nil?
                                     :g-year (s/and int? #(> % -1))
                                     :g-pair :unq/gift-pair)
                              #(<= (:g-year %) (count (:g-hist %)))))
           [nil 0 {:giver :RinStaXX, :givee :PauMccXX}])
=>
[:input-nil
 {:g-hist nil, :g-year 0, :g-pair {:giver :RinStaXX, :givee :PauMccXX}}]

但是当这个"correct"函数被第二个函数使用时:

(defn set-gift-pair-in-roster [plrs-map plr-sym g-year g-pair]
  (let [plr (get-player-in-roster plrs-map plr-sym)
        gh (get-gift-history-in-player plr)
        ngh (set-gift-pair-in-gift-history gh g-year g-pair)
        nplr (set-gift-history-in-player ngh plr)]
    (assoc plrs-map plr-sym nplr)))
(s/fdef set-gift-pair-in-roster
        :args (s/cat :plrs-map ::plr-map
                     :plr-sym keyword?
                     :g-year (s/and int? #(> % -1))
                     :g-pair :unq/gift-pair)
        :ret ::plr-map)

消费函数成为消费函数的错误来源(nil 案例 - 我认为已处理):

(stest/check `set-gift-pair-in-roster)
=>
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451
                0x3bbc704a
                "clojure.spec.alpha$fspec_impl$reify__2451@3bbc704a"],
  :clojure.spec.test.check/ret {:result #error{:cause "Call to #'clojure-redpoint.roster/set-gift-pair-in-gift-history did not conform to spec:
                                                       In: [0] val: nil fails spec: :unq/gift-history at: [:args :input-hist :g-hist] predicate: vector?
                                                       val: {:g-hist nil, :g-year 1, :g-pair {:givee :_+, :giver :RJK/Y24}} fails at: [:args :input-nil] predicate: (<= (:g-year %) (count (:g-hist %)))

我曾尝试更改使用函数中的规范的顺序和分组(嵌套)- 但后来它未能通过它曾经通过的测试,甚至在开始测试使用函数之前。

想知道这里出了什么问题吗?

谢谢!

编辑:

按照建议,这里是完整的代码以便更好地理解:

;Here is an example of The Beatles keeping track of the Xmas gifts
;they give to each other (:giver and :givee) each year over time:

(ns clojure-redpoint.roster2
  (:require [clojure.spec.alpha :as s]
            [orchestra.spec.test :as st]
            [clojure.test.check.generators :as gen]
            [clojure.spec.test.alpha :as stest]))

(s/def ::givee keyword?)
(s/def ::giver keyword?)
(s/def :unq/gift-pair (s/keys :req-un [::givee ::giver]))
(s/def ::name string?)
(s/def :unq/gift-history (s/coll-of :unq/gift-pair :kind vector?))
(s/def :unq/player (s/keys :req-un [::name :unq/gift-history]))
(s/def ::plr-map (s/map-of keyword? :unq/player))


(defn- get-player-in-roster [plrs-map plr-sym]
  (get plrs-map plr-sym))
(s/fdef get-player-in-roster
        :args (s/cat :plrs-map ::plr-map :plr-sym keyword?)
        :ret (s/or :found :unq/player
                   :not-found nil?))

(defn- get-gift-history-in-player [plr]
  (get plr :gift-history))
(s/fdef get-gift-history-in-player
        :args (s/or :input-plr (s/cat :plr :unq/player)
                    :input-nil (s/cat :plr nil?))
        :ret (s/or :found :unq/gift-history
                   :not-found nil?))

(defn set-gift-pair-in-gift-history [g-hist g-year g-pair]
  (if (nil? g-hist)
    [{:giver :none, :givee :none}]
    (assoc g-hist g-year g-pair)))
(s/fdef set-gift-pair-in-gift-history
        :args (s/with-gen
                (s/or :input-hist (s/and
                                    (s/cat :g-hist :unq/gift-history
                                           :g-year (s/and int? #(> % -1))
                                           :g-pair :unq/gift-pair)
                                    #(<= (:g-year %) (count (:g-hist %))))
                      :input-nil (s/and
                                   (s/cat :g-hist nil?
                                          :g-year (s/and int? #(> % -1))
                                          :g-pair :unq/gift-pair)
                                   #(<= (:g-year %) (count (:g-hist %)))))
                #(gen/let [hist (s/gen :unq/gift-history)
                           year (gen/large-integer* {:min 0 :max (max 0 (dec (count hist)))})
                           pair (s/gen :unq/gift-pair)]
                   [hist year pair]))
        :ret :unq/gift-history)

(defn set-gift-history-in-player [g-hist plr]
  (if (or (nil? g-hist) (nil? plr))
    {:name "none", :gift-history [{:giver :none, :givee :none}]}
    (assoc plr :gift-history g-hist)))
(s/fdef set-gift-history-in-player
        :args (s/or :input-good (s/cat :g-hist :unq/gift-history
                                       :plr :unq/player)
                    :input-hist-nil (s/cat :g-hist nil?
                                           :plr :unq/player)
                    :input-plr-nil (s/cat :g-hist :unq/gift-history
                                          :plr nil?)
                    :input-both-nil (s/cat :g-hist nil?
                                           :plr nil?))
        :ret :unq/player)

(defn set-gift-pair-in-roster [plrs-map plr-sym g-year g-pair]
  (let [plr (get-player-in-roster plrs-map plr-sym)
        gh (get-gift-history-in-player plr)
        ngh (set-gift-pair-in-gift-history gh g-year g-pair)
        nplr (set-gift-history-in-player ngh plr)]
    (assoc plrs-map plr-sym nplr)))
(s/fdef set-gift-pair-in-roster
        :args (s/cat :plrs-map ::plr-map
                     :plr-sym keyword?
                     :g-year (s/and int? #(> % -1))
                     :g-pair :unq/gift-pair)
        :ret ::plr-map)

(st/instrument)

(def roster-map
  {:RinSta {:name "Ringo Starr", :gift-history [{:giver :RinSta, :givee :PauMcc}]},
   :JohLen {:name "John Lennon", :gift-history [{:giver :JohLen, :givee :GeoHar}]},
   :GeoHar {:name "George Harrison", :gift-history [{:giver :GeoHar, :givee :JohLen}]},
   :PauMcc {:name "Paul McCartney", :gift-history [{:giver :PauMcc, :givee :RinSta}]}})

(s/conform ::plr-map
           (set-gift-pair-in-roster roster-map :PauMcc 0 {:giver :JohLenXXX, :givee :GeoHarXXX}))
;=>
;{:RinSta {:name "Ringo Starr", :gift-history [{:giver :GeoHar, :givee :JohLen}]},
; :JohLen {:name "John Lennon", :gift-history [{:giver :RinSta, :givee :PauMcc}]},
; :GeoHar {:name "George Harrison",
;          :gift-history [{:giver :PauMcc, :givee :RinSta}]},
; :PauMcc {:name "Paul McCartney",
;          :gift-history [{:giver :JohLenXXX, :givee :GeoHarXXX}]}}

;(stest/check `set-gift-pair-in-roster)

不幸的是,它没有帮助我找到我的错误...

问题是当您 (stest/check `set-gift-pair-in-roster):

CompilerException clojure.lang.ExceptionInfo: Call to #'playground.so/set-gift-pair-in-gift-history did not conform to spec:
In: [0] val: nil fails spec: :unq/gift-history at: [:args :input-hist :g-hist] predicate: vector?
val: {:g-hist nil, :g-year 1, :g-pair {:givee :A, :giver :A}} fails at: [:args :input-nil] predicate: (<= (:g-year %) (count (:g-hist %)))

check 输出为我们提供了重现错误的最小输入:

(set-gift-pair-in-roster {} :A 1 {:givee :A, :giver :A})

我们可以看到失败函数的第一个参数是 nil。查看 set-gift-pair-in-gift-history 的函数规范,有一个可疑的规范涵盖了这种情况:

            :input-nil (s/and
                         (s/cat :g-hist nil?
                                :g-year (s/and int? #(> % -1))
                                :g-pair :unq/gift-pair)
                         #(<= (:g-year %) (count (:g-hist %)))))

这只会在 g-hist 为 nil 且 g-year0 时符合,但是 :g-year 的生成器将生成除 [=20= 之外的许多数字].这就是为什么在 instrumented 时调用它失败的原因。

instrumentcheck 显示程序指定 的行为方式与实际行为方式之间存在差异。我首先考虑当 set-gift-pair-in-gift-history 的第一个参数为 nil 时应该如何指定。当第一个 arg 为 nil 时,实现不关心其他参数,因此您可以调整函数规范以反映这一点:

:input-nil (s/cat :g-hist nil? :g-year any? :g-pair any?)

通过该更改,您的顶级函数应该 check 成功。