Clojure规范:直接检查映射值?
我正在为一个Clojure规范:直接检查映射值?,clojure,clojure.spec,Clojure,Clojure.spec,我正在为一个正方形编写一个规范,它非常简单,是一对整数坐标(键::sq-x::sq-y)和一系列顶点(键::vtxs)的组合 指定此约束: (s/def ::square (s/and map? ; this is probably not needed (s/keys :req [::sq-x ::sq-y ::vtxs]))) 以上仅检查钥匙是否存在。为了也检查键值,我添加了与要检查的键名相同的等级库。规范之间的隐式链接始终处于活动状态: (s/def ::s
正方形
编写一个规范,它非常简单,是一对整数坐标(键::sq-x
::sq-y
)和一系列顶点(键::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
中,是另一个规范检查值integer ness(我们基本上对规范进行了别名:::sq-x
->::int val
):
这非常有效。从另一个软件包(它将上面的软件包作为sut
(“测试中的系统”)导入),我可以运行此测试代码,但有错误。。。“对目标的良好效果”:
到目前为止还不错
现在,复杂性:
在此之前,我曾试图找到一种直接检查映射值的方法,而不需要通过另一个规范。我没有找到一种方法让Clojure喜欢。例如,这不起作用:
(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))))
运行时是痛苦的时刻:
java.lang.IllegalArgumentException: No implementation of method:
:specize* of protocol: #'clojure.spec.alpha/Specize found for class: nil
嗯。那代码看起来很狡猾。有没有一种方法可以直接进入映射,或者我总是应该定义另一个规范并通过命名隐式调用它?我只需要使用内置函数
int?
来定义规范:
(s/def ::sq-x int?)
有关详细信息,请参阅
但是,集合中每个项目的规格都有一个“类型”,因此规格可以重复使用。所以一个::address
规范可能由::number
,::street
,::city
,::state
和::zip
组成
见:
更新: 我编写了一个更通用的整数值测试函数:
(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")))
我总是应该定义另一个规范并通过命名隐式地调用它吗
要按预期/设计使用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(如果它们存在于spec注册表中),并分别与每个键的值一致:
(s/conform ::square {::sq-x 1 ::sq-y 0 ::vtxs ["hey"]})
这里的意图是将规范与强名称/关键字联系起来,以便::sq-x
在任何地方都意味着相同的东西(尽管它实际上是:任何名称空间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))))
为了更好地理解这一点,请尝试单独计算其中一个表单,并确保其计算结果为零
user=> (::int-val #(get % ::sq-x))
nil
相反,您需要的是一个函数,它将被传递一些值,然后返回一个值,或者可能是:clojure.spec.alpha/invalid
。此示例在不注册单个密钥规范的情况下也可以工作,但我认为它与spec的设计不太一致:
(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 %))))
好的,明白了。所以当“spec evaluation”找到一个谓词而不是“spec构造函数”时,它会传递要指定的东西。这导致了在s/和:1)#(让[a(::sq-x%)](=(Math/floor a)(*1.0a))
中执行检查的额外可能性(让[a(=(Math/floor a)(*1.0a))
避免从映射2中重复获取)#(让[a](::sq-x%)](s/valid?::int-val a))
以谓词的形式调用现有规范3)#(s/valid?::int-val(::sq-x%)
这只是2)而没有let
。谢谢Alan,但我想“接受一个整数值,即使键入的是双精度的”,并且(int-4.0)=>错误
。然后我对如何显式地指定值而不是隐式地指定值感兴趣,因此问题就来了。(=x(intx))
是不可行的,因为int
强制转换为整数:(=%(int%))6.0;=>错误
。对称地,floor
转换为double
:(#(=%(数学/floor%))6);=>错误
。你真的必须或两种情况:(#)(或(=%(数学/地板%))(=%(整数%))6);=>正确
和相同的(#)(或(=%(数学/地板%))(=%(整数%))6.0);=>正确
user=> (::int-val #(get % ::sq-x))
nil
(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 %))))