Clojure 使用Taoensso桁架验证地图中可选键下的内容
假设我们有一张描述i圆的地图。圆始终有一个中心,但可以通过直径或半径来描述:Clojure 使用Taoensso桁架验证地图中可选键下的内容,clojure,Clojure,假设我们有一张描述i圆的地图。圆始终有一个中心,但可以通过直径或半径来描述: (require '[taoensso.truss :as truss]) 上述两个圆圈描述的是同一个圆圈 假设我们有一个函数需要一个圆映射作为它的输入,我们怎样才能最好地用truss来断言它呢?代码的开头可能如下所示: {:center [1, 2] :diameter 10} {:center [1, 2] :radius 5} 问题当然是,我们不能直接断言这些键的值具有某些属性,因为它们不一定存在。例如,
(require '[taoensso.truss :as truss])
上述两个圆圈描述的是同一个圆圈
假设我们有一个函数需要一个圆映射作为它的输入,我们怎样才能最好地用truss来断言它呢?代码的开头可能如下所示:
{:center [1, 2] :diameter 10}
{:center [1, 2] :radius 5}
问题当然是,我们不能直接断言这些键的值具有某些属性,因为它们不一定存在。例如,以下声明要求两个键同时出现:
(defn circle-tosser
[circle-map]
(truss/have map? circle-map)
(truss/have number? :in (:center circle-map))
(str "Tossing " circle-map))
(circle-tosser {:center [1, 2] :radius 5})
;; => "Tossing {:center [1 2], :radius 5}"
然后你就可以开始写这样的东西了
(defn circle-tosser
[circle-map]
(truss/have map? circle-map)
(truss/have number? :in (:center circle-map))
(truss/have number? (:diameter circle-map))
(truss/have number? (:radius circle-map))
(str "Tossing " circle-map))
(circle-tosser {:center [1, 2] :diameter 10}) ; Unhandled Exception
(circle-tosser {:center [1, 2] :diameter 10 :radius 5})
;; => "Tossing {:center [1 2], :diameter 10, :radius 5}"
但这开始变得过于冗长,无法阅读,这是不幸的,因为以代码的形式提供可访问的文档是truss的目的之一
也许你能想出一个更好的方法来解决这个问题?有两种方法可以解决你的问题 1始终生成具有半径的圆,而不生成具有直径的圆 2如果不能执行1,则对圆的每个引用都需要包装在转换函数中,如下所示:
(defn circle-tosser
[circle-map]
(truss/have map? circle-map)
(truss/have number? :in (:center circle-map))
(when (contains? circle-map :diameter)
(truss/have number? (:diameter circle-map)))
(when (contains? circle-map :radius)
(truss/have number? (:radius circle-map)))
(str "Tossing " circle-map))
(circle-tosser {:center [1, 2] :diameter 10})
;; => "Tossing {:center [1 2], :diameter 10}"
你的代码看起来像
(defn normalize [c]
(if (contains? c :diameter)
(-> c
(assoc :radius (/ (:diameter c) 2))
(dissoc :diameter))
c))
我总是喜欢解决方案1,但有时无法避免解决方案2。您可以定义一个函数来隐藏重复的代码,如
(defn circle-tosser
[circle-map]
(let [c (normalize circle-map) ]
(truss/have map? c)
(truss/have number? :in (:center c))
(str "Tossing " c)))
在这个例子中,左图当然是引入了相互排他性的概念。但是,正如艾伦·汤普森所建议的那样,对数据进行规范化可能更好。我知道你的点对圆的例子是如何有效的。也许我使用了一个非真实世界的例子,这是错误的。相反,假设我们有一个配置映射,它有许多不同的可选键,我们想要验证这些键的值,比如leiningen project.clj。你认为我应该用这个例子写一篇新文章吗?
(defn opt-key
[key predicate data]
(when (contains? data key)
(truss/have predicate (get data key))))
(defn circle-tosser
[circle-map]
(truss/have map? circle-map)
(truss/have number? :in (:center circle-map))
(opt-key :diameter number? circle-map)
(opt-key :radius number? circle-map)
(str "Tossing " circle-map))
(circle-tosser {:center [1, 2] :diameter 10})
;; => "Tossing {:center [1 2], :diameter 10}"