我正在为square
写一个规范,它非常简单,是一对整数坐标(键::sq-x
::sq-y
)与顶点的顺序集合(键{{ 1}})。
说明此约束:
::vtxs
以上内容仅检查密钥的存在。为了还检查键值,我添加了规范,其名称与要检查的键相同。规格之间的这种隐式链接始终有效:
(s/def ::square
(s/and
map? ; this is probably not needed
(s/keys :req [::sq-x ::sq-y ::vtxs])))
在上面的(s/def ::sq-x ::int-val)
(s/def ::sq-y ::int-val)
(s/def ::vtxs sequential?)
中,是另一个检查整数值的规范(我们基本上是在规范上使用别名:::int-val
-> ::sq-x
):
::int-val
这非常好用。从另一个将上面的程序包导入为(s/def ::int-val #(= (Math/floor %) (* 1.0 %)))
(“被测系统”)的程序包中,我可以运行此测试代码,并显示err ...“对目标效果良好”:
sut
到目前为止很好。
现在,并发症:
在此之前,我试图找到一种方法来直接检查地图值,而无需通过其他规范。我没有找到使Clojure变得可口的方法。例如,这不起作用:
(t/deftest test-good-squares
(t/is (s/valid? ::sut/square
{ ::sut/sq-x 1 ::sut/sq-y -1 ::sut/vtxs [] }))
(t/is (s/valid? ::sut/square
{ ::sut/sq-x 5.0 ::sut/sq-y 5.0 ::sut/vtxs [] }))
(t/is (s/valid? ::sut/square
{ ::sut/sq-x 0.0 ::sut/sq-y 0.0 ::sut/vtxs [] })))
(t/deftest test-bad-squares-bad-coords
(t/is (not (s/valid? ::sut/square
{ ::sut/sq-x 1.1 ::sut/sq-y -1 ::sut/vtxs [] })))
(t/is (not (s/valid? ::sut/square
{ ::sut/sq-x -1 ::sut/sq-y 1.1 ::sut/vtxs [] })))
(t/is (not (s/valid? ::sut/square
{ ::sut/sq-x 1.1 ::sut/sq-y 1.1 ::sut/vtxs [] }))))
(t/deftest test-bad-squares-bad-vertexes
(t/is (not (s/valid? ::sut/square
{ ::sut/sq-x 1.1 ::sut/sq-y -1 ::sut/vtxs #{1 2 3} }))))
(t/deftest test-bad-squares-bad-type
(t/is (not (s/valid? ::sut/square [:a :b :c]))))
(t/deftest test-bad-squares-missing-keys
(t/is (not (s/valid? ::sut/square { ::sut/sq-y 0 ::sut/vtxs [] })))
(t/is (not (s/valid? ::sut/square { ::sut/sq-x 0 ::sut/vtxs [] })))
(t/is (not (s/valid? ::sut/square { ::sut/vtxs [] }))))
; call the above hierarchically
(t/deftest test-square
(test-good-squares)
(test-bad-squares-bad-coords)
(test-bad-squares-bad-vertexes)
(test-bad-squares-bad-type)
(test-bad-squares-missing-keys))
; call ONLY the test-square from "lein test", don't call individual
; tests a second time
(defn test-ns-hook [] (test-square))
运行时间是非常宝贵的时间:
(s/def ::square
(s/and
map?
(s/keys :req [::sq-x ::sq-y ::vtxs])
(::int-val #(get % ::sq-x))
(::int-val #(get % ::sq-y))
(sequential? #(get % ::vtxs))))
好的。该代码看起来很狡猾。有没有办法直接进入地图,还是我总是应该定义另一个规范并通过命名隐式调用它?
答案 0 :(得分:3)
我总是应该定义另一个规范并通过命名隐式调用它吗?
要按预期/设计目的使用clojure.spec,自然的方法是按照此处的步骤注册关键规格:
(s/def ::sq-x ::int-val)
(s/def ::sq-y ::int-val)
(s/def ::vtxs sequential?)
这为关键字::sq-x
,::sq-y
等赋予了“全局”含义。使用这种方法,您可以使用这些键为地图定义s/keys
规范:
(s/def ::square (s/keys :req [::sq-x ::sq-y ::vtxs]))
然后,如果您根据::square
映射,则spec将解析每个键的规范(如果它们存在于spec注册表中)并分别符合每个键的值:
(s/conform ::square {::sq-x 1 ::sq-y 0 ::vtxs ["hey"]})
这里的目的是将规范与强名称/关键字相关联,以便::sq-x
到处都具有相同的含义(尽管实际上是:whatever-namespace-foo/sq-x
。
有没有办法直接进入地图
是的,您当然可以定义自定义谓词/函数来检查/整合所需的任何数据。上面的示例有两个问题:
(s/def ::square
(s/and
map? ;; unnecessary with s/keys
(s/keys :req [::sq-x ::sq-y ::vtxs])
;; the following forms don't evaluate to functions, so they aren't used as predicates
(::int-val #(get % ::sq-x))
(::int-val #(get % ::sq-y))
(sequential? #(get % ::vtxs))))
要更好地理解这一点,请尝试分别评估其中一种形式,并查看结果为nil。
user=> (::int-val #(get % ::sq-x))
nil
您想要的是一个将传递一些值并返回值或返回:clojure.spec.alpha/invalid
的函数。此示例无需注册个别关键规格即可工作,但我认为它与规格的设计不太吻合:
(s/def ::square
(s/and
(s/keys :req [::sq-x ::sq-y ::vtxs])
#(= (Math/floor (::sq-x %)) (* 1.0 (::sq-x %)))
#(= (Math/floor (::sq-y %)) (* 1.0 (::sq-y %)))
#(sequential? (::vtxs %))))
答案 1 :(得分:2)
我只是使用内置函数int?
定义规格:
(s/def ::sq-x int?)
有关详细信息,请参见:https://clojure.org/guides/spec#_composing_predicates。
但是,规范要求集合中的每个项目都具有一个“类型”,因此规范可以重复使用。因此,::address
规范可能由::number
,::street
,::city
,::state
和::zip
组成。
请参阅:https://clojure.org/guides/spec#_entity_maps
更新:
我写了一个更通用的整数值测试函数:
(ns tst.demo.core
(:use demo.core tupelo.test)
(:require [tupelo.core :as t]))
(defn int-val?
"Returns true iff arg is an integer value of any Clojure/Java type
(all int types, float/double, BigInt/BigInteger, BigDecimal, clojure.lang.Ratio)."
[x]
(cond
(or (int? x) (integer? x)) true
; handles both java.lang.Float & java.lang.Double types
(float? x) (let [x-dbl (double x)] (= x-dbl (Math/floor x-dbl)))
(bigdecimal? x) (try
(let [bi-val (.toBigIntegerExact x)]
; no exception => fraction was zero
true)
(catch Exception e
; exception => fraction was non-zero
false))
(ratio? x) (zero? (mod x 1))
:else (throw (ex-info "Invalid type" {:x x}))))
(dotest
(is (not= 5 5.0))
(is (int-val? 5))
(is (int-val? 5.0))
(is (int-val? 5N))
(is (int-val? 5M))
(is (int-val? (bigdec 5)))
(is (int-val? (bigint 5)))
(is (int-val? (biginteger 5)))
(is (int-val? (* 3 (/ 5 3)) ))
(throws? (int-val? "five")))