Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Clojure规范:直接检查映射值?_Clojure_Clojure.spec - Fatal编程技术网

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 %))))