具有相互依赖值的哈希映射的 clojure 规范?

clojure spec for hash-map with interdependent values?

我想为散列映射编写一个 clojure 规范,其中 键被限制为等于其他两个键的值的总和。我 知道一种手动为此类规范编写测试生成器的方法:

(ns my-domain)
(require '[clojure.test           :refer :all     ]
         '[clojure.spec.alpha     :as s           ]
         '[clojure.spec.gen.alpha :as gen         ]
         '[clojure.pprint         :refer (pprint) ])

(s/def ::station-id string?)
(s/def ::sim-time (s/double-in :infinite? true, :NaN? false))
(s/def ::reserved-counts (s/and int? #(not (neg? %))))
(s/def ::free-counts     (s/and int? #(not (neg? %))))

(def counts-preimage (s/gen (s/keys :req [::station-id
                                          ::sim-time
                                          ::reserved-counts
                                          ::free-counts])))

(pprint (gen/generate
         (gen/bind
          counts-preimage
          #(gen/return
            (into % {::total-counts
                     (+ (::reserved-counts %)
                        (::free-counts %))})))))
#:my-domain{:station-id "sHN8Ce0tKWSdXmRd4e46fB",
            :sim-time -3.4619293212890625,
            :reserved-counts 58,
            :free-counts 194,
            :total-counts 252}

但我还没有想出如何为它编写规范,更不用说规范了 产生一个类似的发电机。问题的要点是,在规范的 space 中,我缺少一种方法来 掌握规范中的 "preimage" ,也就是说,我缺少与 bind 类似的东西 来自 space 个生成器。这是一次失败的尝试:

(s/def ::counts-partial-hash-map
  (s/keys :req [::station-id
                ::sim-time
                ::reserved-counts
                ::free-counts]))
(s/def ::counts-attempted-hash-map
  (s/and ::counts-partial-hash-map
         #(into % {::total-counts (+ (::reserved-counts %)
                                     (::free-counts %))})))

(pprint (gen/generate (s/gen ::counts-attempted-hash-map)))
#:my-domain{:station-id "ls5qBUoF",
            :sim-time ##Inf,
            :reserved-counts 56797960,
            :free-counts 17}

生成的样本符合规范,因为 #(into % {...}) 是真实的, 但结果不包含键为 ::total-counts.

的新属性

如有任何指导,我将不胜感激。

编辑:今天我了解了 s/with-gen,这将使我能够附加 我的(工作中的)测试生成器到我的 "preimage" 或 "partial" 规范。也许 这是最好的前进方式吗?

您可以使用 nat-int? 谓词(为此有一个 built-in 规范,感谢@glts)作为计数键,并添加一个 ::total-counts 规范:

(s/def ::reserved-counts nat-int?)
(s/def ::free-counts nat-int?)
(s/def ::total-counts nat-int?)

(s/def ::counts-partial-hash-map
  (s/keys :req [::station-id
                ::sim-time
                ::reserved-counts
                ::free-counts]))

spec for a hash-map wherein the value of one of the keys is constrained to be equal to the sum of the values of two other keys

要添加此断言,您可以 s/and 具有 keys 规范的谓词函数(或者在此示例中 merge 规范将部分映射规范与 ::total-count 按键规格):

(s/def ::counts-attempted-hash-map
  (s/with-gen
    ;; keys spec + sum-check predicate
    (s/and
      (s/merge ::counts-partial-hash-map (s/keys :req [::total-counts]))
      #(= (::total-counts %) (+ (::reserved-counts %) (::free-counts %))))
    ;; custom generator
    #(gen/fmap
       (fn [m]
         (assoc m ::total-counts (+ (::reserved-counts m) (::free-counts m))))
       (s/gen ::counts-partial-hash-map))))

这也使用 with-gen 将自定义生成器与将 ::total-count 设置为其他计数键之和的规范相关联。

(gen/sample (s/gen ::counts-attempted-hash-map) 1)
=> (#:user{:station-id "", :sim-time 0.5, :reserved-counts 1, :free-counts 1, :total-counts 2})

The generated sample conforms to the spec because #(into % {...}) is truthy, but the result doesn't contain the new attribute with the key ::total-counts.

我建议不要将规格 calculate/add ::total-counts 用于地图。规范通常不应用于数据转换。