Clojure 规范的正确嵌套?
Proper nesting of Clojure Specs?
我想我可能对函数中嵌套规范的正确顺序有疑问 - 特别是 s/with-gen
和 s/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-year
为 0
时符合,但是 :g-year
的生成器将生成除 [=20= 之外的许多数字].这就是为什么在 instrument
ed 时调用它失败的原因。
instrument
和 check
显示程序指定 的行为方式与实际行为方式之间存在差异。我首先考虑当 set-gift-pair-in-gift-history
的第一个参数为 nil 时应该如何指定。当第一个 arg 为 nil 时,实现不关心其他参数,因此您可以调整函数规范以反映这一点:
:input-nil (s/cat :g-hist nil? :g-year any? :g-pair any?)
通过该更改,您的顶级函数应该 check
成功。
我想我可能对函数中嵌套规范的正确顺序有疑问 - 特别是 s/with-gen
和 s/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-year
为 0
时符合,但是 :g-year
的生成器将生成除 [=20= 之外的许多数字].这就是为什么在 instrument
ed 时调用它失败的原因。
instrument
和 check
显示程序指定 的行为方式与实际行为方式之间存在差异。我首先考虑当 set-gift-pair-in-gift-history
的第一个参数为 nil 时应该如何指定。当第一个 arg 为 nil 时,实现不关心其他参数,因此您可以调整函数规范以反映这一点:
:input-nil (s/cat :g-hist nil? :g-year any? :g-pair any?)
通过该更改,您的顶级函数应该 check
成功。