Clojure 使用Taoensso桁架验证地图中可选键下的内容

Clojure 使用Taoensso桁架验证地图中可选键下的内容,clojure,Clojure,假设我们有一张描述i圆的地图。圆始终有一个中心,但可以通过直径或半径来描述: (require '[taoensso.truss :as truss]) 上述两个圆圈描述的是同一个圆圈 假设我们有一个函数需要一个圆映射作为它的输入,我们怎样才能最好地用truss来断言它呢?代码的开头可能如下所示: {:center [1, 2] :diameter 10} {:center [1, 2] :radius 5} 问题当然是,我们不能直接断言这些键的值具有某些属性,因为它们不一定存在。例如,

假设我们有一张描述i圆的地图。圆始终有一个中心,但可以通过直径或半径来描述:

(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}"