【问题标题】:How to check the resolvability of Clojure specs?如何检查 Clojure 规范的可解析性?
【发布时间】:2017-08-22 13:42:42
【问题描述】:

clojure.spec.alpha 允许在定义新规范时使用不可解析的规范:

(s/def :foo/bar (s/or :nope :foo/foo))

这里,:foo/foo 无法解析,因此使用 :foo/bar 会引发使用异常:

(s/valid? :foo/bar 42)
;; Exception Unable to resolve spec: :foo/foo  clojure.spec.alpha/reg-resolve! (alpha.clj:69)

当我输入:my-ns/my-spec 而不是::my-ns/my-spec 之类的拼写错误时,我的代码中就会发生这种情况。我想用单元测试来捕捉那些。

clojure.spec.alpha source code 中潜水,我发现我可以使用(keys (s/registry)) 获得所有规格,所以我的测试如下所示:

(ns my-ns.spec-test
  (:require [clojure.test :refer :all]
            [clojure.spec.alpha :as s]
            ;; :require all the relevant namespaces to populate the
            ;; global spec registry.
            [my-ns.spec1]
            [my-ns.spec2]))

(deftest resolvable-specs
  (doseq [spec (keys (s/registry))]
    (is (resolvable? spec))))
    ;;   ^^^^^^^^^^^ placeholder; that’s the function I want

不幸的是,clojure.spec.alpha 中没有 s/resolvable? 这样的东西。到目前为止我发现的唯一解决方案是调用 (s/valid? spec 42) 并假设它没有引发异常意味着它是可解决的,但它不会检查所有分支:

(s/def :int/int int?)
(s/def :bool/bool bool?)

(s/def :my/spec (s/or :int :int/int
                      :other (s/or :bool bool/bool
                                   :nope :idont/exist)))

(s/valid? :my/spec 1) ; <- matches the :int branch
;; => true

(s/valid? :my/spec :foo)
;; Exception Unable to resolve spec: :idont/exist  clojure.spec.alpha/reg-resolve! (alpha.clj:69)

我检查了异常堆栈跟踪以及源代码,看看是否可以找到任何函数来完全解析规范,而无需使用上面的 42:foo 之类的测试值,但找不到任何函数。

有没有办法检查,对于给定的规范,它在其所有分支中引用的所有规范确实存在?

【问题讨论】:

    标签: clojure clojure.spec


    【解决方案1】:

    我能够做到以下几点:

    (ns my-ns.utils
      (:require [clojure.spec.alpha :as s]))
    
    (defn- unresolvable-spec
      [spec]
      (try
        (do (s/describe spec) nil)
        (catch Exception e
          (if-let [[_ ns* name*] (re-matches #"Unable to resolve spec: :([^/]+)/(.+)$" (.getMessage e))]
            (keyword ns* name*)
            (throw e)))))
    
    (defn unresolvable?
      "Test if a spec is unresolvable, and if so return a sequence
       of the unresolvable specs it refers to."
      [spec]
      (cond
        (symbol? spec)
          nil
    
        (keyword? spec)
          (if-let [unresolvable (unresolvable-spec spec)]
            [unresolvable]
            (not-empty (distinct (unresolvable? (s/describe spec)))))
    
        (seq? spec)
          (case (first spec)
            or (->> spec (take-nth 2) rest (mapcat unresolvable?))
            and (->> spec rest (mapcat unresolvable?))
    
            ;; undecidable
            nil)
    
        :default (unresolvable-spec spec)))
    
    (def resolvable? (complement unresolvable?))
    

    它适用于 s/ands/or,这是我的最小用例:

    (u/resolvable? :int/int) ;; => true
    (u/resolvable? :my/spec) ;; => false
    (u/unresolvable? :my/spec) ;; => (:idont/exist)
    

    但它有一些缺陷:

    • 它重新发明了轮子;我认为那些规范遍历功能已经存在于clojure.spec.alpha 的某个地方
    • 它依赖于捕获异常然后解析其消息,因为 (1) clojure.spec.alpha 没有不引发异常的函数,并且 (2) 引发异常的函数不使用任何更具体的函数比Exception

    如果有人有更强大的东西,我很乐意接受任何其他答案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多