将 Clojure 的关联抽象扩展到 Java 库类型
Extending Clojure's Associative Abstraction to Java Library Types
我有一个应用程序(实际上有几个)使用 Jackson 解码 Map 中的 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 不是一个协议(如果它看起来是可行的)。第二个是类型已经定义,所以我不能添加 Associative with deftype.
我对 Clojure 的 JVM 互操作部分还很陌生。有没有我没有看到的方法?或者是否有一个协议可以包装 Associative 并将与我错过的 get-in
一起工作?
非常感谢!
答案是你想做的扩展一半已经做了,另一半做不了。 get-in
function calls get
, which calls clojure.lang.RT/get
, which calls clojure.lang.RT/getFrom
, which calls java.util.Map/get
if the first argument is a Map
. So if you have any Java Map
, then get-in
works (I'm borrowing this example directly from the doto
文档字符串):
(let [m (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))]
[(get-in m ["b"])
(get-in m ["a"])])
;;=> [2 1]
但是,Clojure 没有 List
s that support RandomAccess
的 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 的 get-in
和任何其他已经使用 Clojure 的 get
的代码仍然无法使用 ArrayList
。不幸的是,我认为没有真正好的解决方案来解决您的问题。
我有一个应用程序(实际上有几个)使用 Jackson 解码 Map 中的 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 不是一个协议(如果它看起来是可行的)。第二个是类型已经定义,所以我不能添加 Associative with deftype.
我对 Clojure 的 JVM 互操作部分还很陌生。有没有我没有看到的方法?或者是否有一个协议可以包装 Associative 并将与我错过的 get-in
一起工作?
非常感谢!
答案是你想做的扩展一半已经做了,另一半做不了。 get-in
function calls get
, which calls clojure.lang.RT/get
, which calls clojure.lang.RT/getFrom
, which calls java.util.Map/get
if the first argument is a Map
. So if you have any Java Map
, then get-in
works (I'm borrowing this example directly from the doto
文档字符串):
(let [m (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))]
[(get-in m ["b"])
(get-in m ["a"])])
;;=> [2 1]
但是,Clojure 没有 List
s that support RandomAccess
的 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 的 get-in
和任何其他已经使用 Clojure 的 get
的代码仍然无法使用 ArrayList
。不幸的是,我认为没有真正好的解决方案来解决您的问题。