将Clojure的关联抽象扩展到Java库类型

时间:2017-08-30 00:33:00

标签: clojure clojure-java-interop

我有一个应用程序(实际上有几个)使用Jackson解码地图中的JSON数据。数据似乎位于Map或ArrayList中(在JSON数组的情况下。)这些流中的数据是非结构化的,因此不会发生变化。

我拥有一些Clojure代码,可以访问这些对象中的嵌套属性。理想情况下,我想将关联抽象扩展到这些Java类型,以便get-in对它们起作用。如下所示:

(extend-protocol clojure.lang.Associative
  java.util.Map
    (containsKey [this k] (.containsKey this k))
    (entryAt [this k] (when (.containsKey this k)
                  (clojure.lang.MapEntry/create k (.get this k))))
java.util.ArrayList
  (containsKey [this k] (< (.size this) k))
  (entryAt [this k] (when (.containsKey this k)
                  (clojure.lang.MapEntry/create k (.get this k)))))

这有两个问题;第一个是Associative不是一个协议(如果它似乎它会起作用)。第二个是类型已经定义,所以我不能添加与deftype的关联。

我对Clojure的JVM互操作部分很陌生。有没有我看不到的方法?或者是否有一个包含Associative的协议,它将与我错过的get-in一起使用?

非常感谢!

1 个答案:

答案 0 :(得分:2)

答案是你想要做的扩展的一半已经完成,另一半不能完成。 get-in函数调用get,调用clojure.lang.RT/get,调用clojure.lang.RT/getFrom,如果第一个参数为Map则调用java.util.Map/get。因此,如果您有任何Java Map,那么get-in可以正常工作(我直接从doto docstring借用此示例):

(let [m (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))]
  [(get-in m ["b"])
   (get-in m ["a"])])
;;=> [2 1]

但是,Clojure没有支持ListRandomAccess get实施。你可以编写你自己的get

(ns sandbox.core
  (:refer-clojure :exclude [get])
  (:import (clojure.lang RT)
           (java.util ArrayList List RandomAccess)))

(defn get
  ([m k]
   (get m k nil))
  ([m k not-found]
   (if (and (every? #(instance? % m) [List RandomAccess]) (integer? k))
     (let [^List m m
           k (int k)]
       (if (and (<= 0 k) (< k (.size m)))
         (.get m k)
         not-found))
     (RT/get map key not-found))))

示例:

(get (ArrayList. [:foo :bar :baz]) 2)
;;=> :bar

然后您可以复制get-in的实施,以便它可以使用您的自定义get功能。

我很确定这不是你想要的,因为那时你编写的每一段代码都必须使用你的 get-in而不是Clojure的{{1}已经使用Clojure的get-in的任何其他代码仍然无法与get一起使用。不幸的是,我不认为你的问题真的有一个很好的解决方案。