conj - Clojure: trabajar con java.util.HashMap de una manera idiomática de Clojure
reduce clojure (4)
Tengo un objeto java.util.HashMap
m
(un valor de retorno de una llamada a un código Java) y me gustaría obtener un nuevo mapa con un par clave-valor adicional.
Si m
fuera un mapa de Clojure, podría usar:
(assoc m "key" "value")
Pero intentar eso en un HashMap
da:
java.lang.ClassCastException: java.util.HashMap no se puede convertir a clojure.lang.Associative
Tampoco hubo suerte con los seq
:
(assoc (seq m) "key" "value")
java.lang.ClassCastException: clojure.lang.IteratorSeq no se puede convertir a clojure.lang.Associative
La única forma en que logré hacerlo fue usar la propia opción de HashMap
, pero eso devuelve void
así que tengo que devolver explícitamente m
:
(do (. m put "key" "value") m)
Esto no es un código idiomático de Clojure, además estoy modificando m
lugar de crear un nuevo mapa.
¿Cómo trabajar con un HashMap
de una manera más Clojure-ish?
Clojure hace que las colecciones de java sean posibles, por lo que puede utilizar directamente las funciones de secuencia de Clojure en java.util.HashMap .
Pero assoc espera un clojure.lang.Associative así que primero tendrá que convertir el java.util.HashMap a eso:
(assoc (zipmap (.keySet m) (.values m)) "key" "value")
Edición: solución más sencilla:
(assoc (into {} m) "key" "value")
Está totalmente bien usar el mapa hash de java de la manera tradicional.
(do (. m put "key" "value") m)
This is not idiomatic Clojure code, plus I''m modifying m instead of creating a new map.
Está modificando una estructura de datos que realmente se pretende modificar. El mapa hash de Java carece de la compartición estructural que permite que los mapas Clojures se copien de manera eficiente. La forma generalmente idiomática de hacer esto es usar las funciones java-interop para trabajar con las estructuras java en la forma java típica, o convertirlas limpiamente en estructuras Clojure y trabajar con ellas en la forma funcional Clojure. A menos, por supuesto, hace la vida más fácil y da como resultado un mejor código; entonces todas las apuestas están apagadas.
Este es un código que escribí usando hashmaps cuando intentaba comparar las características de memoria de la versión de clojure y las de java (pero se usó de 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))
Esto es para tomar alguna recopilación y devolver cuántas veces se reutiliza cada cosa diferente (digamos una palabra en una cadena).
Si está interactuando con el código Java, es posible que tenga que morder la bala y hacerlo de la manera Java, utilizando .put
. Esto no es necesariamente un pecado mortal; Clojure te da cosas como do
y .
específicamente para que pueda trabajar con código Java fácilmente.
assoc
solo funciona en estructuras de datos Clojure porque se ha trabajado mucho para que sea muy barato crear nuevas copias (inmutables) de ellas con leves alteraciones. Los Java HashMaps no están diseñados para funcionar de la misma manera. Tendría que seguir clonándolos cada vez que realice una alteración, lo que puede ser costoso.
Si realmente quiere salir de Java mutation-land (por ejemplo, tal vez esté manteniendo estos HashMaps durante mucho tiempo y no desee realizar llamadas a Java por todos lados) o necesita serializarlos mediante print
y read
, o desea trabajar con ellos de una manera segura para subprocesos utilizando Clojure STM) puede convertir fácilmente entre HashMaps de Java y hash-maps de Clojure, porque las estructuras de datos de Clojure implementan las interfaces Java correctas para que puedan comunicarse entre sí.
user> (java.util.HashMap. {:foo :bar})
#<HashMap {:foo=:bar}>
user> (into {} (java.util.HashMap. {:foo :bar}))
{:foo :bar}
Si quieres una cosa similar a la de do
que devuelva el objeto en el que estás trabajando una vez que hayas terminado de trabajar en él, puedes usar doto
. De hecho, se utiliza un HashMap de Java como ejemplo en la documentación oficial de esta función, que es otra indicación de que no es el fin del mundo si utiliza objetos Java (con criterio).
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))
Algunas estrategias posibles:
Limite su mutación y sus efectos secundarios a una sola función, si puede. Si su función siempre devuelve el mismo valor dadas las mismas entradas, puede hacer lo que quiera internamente. A veces, mutar una matriz o un mapa es la forma más eficiente o sencilla de implementar un algoritmo. Seguirá disfrutando de los beneficios de la programación funcional siempre y cuando no "filtre" los efectos secundarios hacia el resto del mundo.
Si sus objetos van a estar disponibles por un tiempo o si necesitan jugar bien con otro código de Clojure, intente incluirlos en las estructuras de datos de Clojure tan pronto como pueda, y vuelva a colocarlos en Java HashMaps en el último segundo (cuando se alimente). de vuelta a Java).