Подтвердить что ты не робот

Clojure: работа с java.util.HashMap в идиоматическом стиле Clojure

У меня есть объект java.util.HashMap m (возвращаемое значение из вызова кода Java), и я хотел бы получить новую карту с дополнительной парой ключ-значение.

Если m была картой Clojure, я мог бы использовать:

(assoc m "key" "value")

Но попытка на a HashMap дает:

java.lang.ClassCastException: java.util.HashMap нельзя отнести к clojure.lang.Associative

Не повезло с seq:

(assoc (seq m) "key" "value")

java.lang.ClassCastException: clojure.lang.IteratorSeq нельзя отнести к clojure.lang.Associative

Единственное, что мне удалось сделать, это использовать HashMap собственный put, но возвращает void, поэтому я должен явно возвращать m:

(do (. m put "key" "value") m)

Это не идиоматический код Clojure, а я изменяю m вместо создания новой карты.

Как работать с HashMap более чем через Clojure -ий способ?

4b9b3361

Ответ 1

Clojure делает java Collections seq-able, поэтому вы можете напрямую использовать функции последовательности Clojure на java.util.HashMap.

Но assoc ожидает clojure.lang.Associative, поэтому вам нужно сначала преобразовать java.util.HashMap в это:

(assoc (zipmap (.keySet m) (.values m)) "key" "value")

Изменить: более простое решение:

(assoc (into {} m) "key" "value")

Ответ 2

Если вы взаимодействуете с кодом Java, вам, возможно, придется укусить пулю и сделать это способом Java, используя .put. Это не обязательно смертный грех; Clojure предоставляет вам такие вещи, как do и ., чтобы вы могли легко работать с кодом Java.

assoc работает только с структурами данных Clojure, потому что большая работа позволила сделать его очень дешевым для создания новых (неизменных) копий с небольшими изменениями. Java HashMaps не предназначены для работы одинаково. Вам нужно будет клонировать их каждый раз, когда вы делаете изменения, которые могут быть дорогими.

Если вы действительно хотите выйти из Java-мутации-земли (например, возможно, вы сохраняете эти HashMaps в течение длительного времени и не хотите, чтобы Java-вызовы повсюду, или вам нужно сериализовать их через print и read, или вы хотите работать с ними поточно-безопасным способом с помощью Clojure STM), вы можете легко конвертировать между хэш-картами Java HashMaps и Clojure, поскольку структуры данных Clojure реализуют правые интерфейсы Java, чтобы они могли разговаривать друг с другом.

user> (java.util.HashMap. {:foo :bar})
#<HashMap {:foo=:bar}>

user> (into {} (java.util.HashMap. {:foo :bar}))
{:foo :bar}

Если вы хотите, чтобы объект do, возвращающий объект, над которым вы работаете, как только вы закончите работу над ним, вы можете использовать doto. Фактически, Java HashMap используется в качестве примера в официальной документации для этой функции, что является еще одним признаком того, что это не конец света, если вы используете Java-объекты (разумно).

clojure.core/doto
([x & forms])
Macro
  Evaluates x then calls all of the methods and functions with the
  value of x supplied at the front of the given arguments.  The forms
  are evaluated in order.  Returns x.

  (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))

Некоторые возможные стратегии:

  • Ограничьте свои мутации и побочные эффекты до одной функции, если сможете. Если ваша функция всегда возвращает одно и то же значение с одинаковыми входами, она может делать то, что хочет внутренне. Иногда мутация массива или карты является наиболее эффективным или простым способом реализации алгоритма. Вы по-прежнему будете пользоваться преимуществами функционального программирования, если вы не "утешаете" побочные эффекты для остального мира.

  • Если ваши объекты будут работать некоторое время или им нужно хорошо поиграть с другим кодом Clojure, попробуйте как можно скорее получить их в структурах данных Clojure и отбросить их обратно в Java HashMaps в последнюю секунду (при загрузке обратно на Java).

Ответ 3

Вполне нормально использовать хэш-карту java традиционным способом.
(do (. m put "key" "value") m)
This is not idiomatic Clojure code, plus I'm modifying m instead of creating a new map.

Вы изменяете структуру данных, которая действительно предназначена для изменения. В карте хеша Java отсутствует структурный обмен, который позволяет эффективно копировать карту Clojures. Обычно идиоматический способ сделать это - использовать функции java-interop для работы с структурами Java в типичном java-способе или для чистого преобразования их в структуры Clojure и работать с ними в функциональном Clojure способе. Если, конечно, это не облегчит жизнь и не приведет к улучшению кода; то все ставки отключены.

Ответ 4

Это код, который я написал, используя хэшмапы, когда я пытался сравнить характеристики памяти версии clojure vs java (но использовался из clojure)

(import '(java.util Hashtable))
(defn frequencies2 [coll]
    (let [mydict (new Hashtable)]
      (reduce (fn [counts x]
            (let [y (.toLowerCase x)]
              (if (.get mydict y)
            (.put mydict y (+ (.get mydict y) 1))
            (.put mydict y 1)))) coll) mydict))

Это нужно взять некоторую коллекцию и вернуть, сколько раз каждая другая вещь (например, слово в строке) используется повторно.