Расширение ассоциативной абстракции Clojure на типы библиотек Java

У меня есть приложение (на самом деле несколько), которое декодирует данные JSON на карте с помощью Jackson. Кажется, что данные находятся либо в 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 с deftype.

Я новичок в части взаимодействия JVM с Clojure. Есть ли способ, которого я не вижу? Или есть протокол, который обертывает Associative и будет работать с get-in, который я пропустил?

Спасибо ТАК!


person Cody    schedule 30.08.2017    source источник


Ответы (1)


Ответ заключается в том, что половина расширения, которое вы хотите сделать, уже сделано, а другую половину сделать невозможно. get-in вызывает функцию get, который вызывает clojure.lang.RT/get, который вызывает clojure.lang.RT/getFrom, который вызывает java.util.Map/get, если первым аргументом является Map. Так что если у вас есть Java Map, то get-in работает (этот пример я позаимствовал непосредственно из 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 нет get реализации для List< /a>, которые поддерживают RandomAccess. Вы можете написать свой собственный 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, а не get-in Clojure, и любой другой код, который уже использует get Clojure. все равно не будет работать с ArrayLists. Я не думаю, что есть действительно хорошее решение вашей проблемы, к сожалению.

person Sam Estep    schedule 30.08.2017
comment
Спасибо, Сэм, полезно услышать, что на самом деле нет хорошего решения этой проблемы. Мне очень повезло, что на самом деле все сводится к нескольким сайтам для звонков, и в настоящее время я придерживаюсь аналогичного подхода. Я, вероятно, уменьшу его еще больше и воспользуюсь RT / get по вашему предложению. Спасибо! - person Cody; 30.08.2017