Clojure:Arbeiten mit einer java.util.HashMap in einer idiomatischen Clojure-Manier
-
13-09-2019 - |
Frage
Ich habe ein java.util.HashMap
Objekt m
(ein Rückgabewert von einem Aufruf von Java-Code) und ich möchte eine neue Karte mit einem zusätzlichen Schlüssel-Wert-Paar erhalten.
Wenn m
Wäre eine Clojure-Karte, könnte ich Folgendes verwenden:
(assoc m "key" "value")
Aber versuchen Sie es mal mit einem HashMap
gibt:
java.lang.ClassCastException:java.util.HashMap kann nicht in clojure.lang.Associative umgewandelt werden
Kein Glück damit seq
entweder:
(assoc (seq m) "key" "value")
java.lang.ClassCastException:clojure.lang.IteratorSeq kann nicht in clojure.lang.Associative umgewandelt werden
Der einzige Weg, den ich geschafft habe, war die Verwendung HashMap
ist sein eigenes put
, aber das kommt zurück void
also muss ich explizit zurückkehren m
:
(do (. m put "key" "value") m)
Dies ist kein idiomatischer Clojure-Code, außerdem ändere ich ihn m
anstatt eine neue Karte zu erstellen.
So arbeiten Sie mit einem HashMap
auf eine Clojure-artigere Art und Weise?
Lösung
Clojure macht die Java-Kollektionen Seq-fähig, so dass Sie direkt die Clojure Sequenzfunktionen auf dem java.util.HashMap verwenden können.
Aber Assoc erwartet ein clojure.lang.Associative so dass Sie müssen zuerst konvertieren die java.util.HashMap zu, dass:
(assoc (zipmap (.keySet m) (.values m)) "key" "value")
Edit: einfachere Lösung:
(assoc (into {} m) "key" "value")
Andere Tipps
Wenn Sie mit Java-Code sind eine Schnittstelle, können Sie die Kugel beißen und tun es die Java Art und Weise, .put
verwenden. Dies ist nicht unbedingt eine Todsünde; Clojure gibt Ihnen Dinge wie do
und .
speziell so dass Sie leicht mit Java-Code arbeiten.
assoc
funktioniert nur auf Clojure Datenstrukturen, weil viel Arbeit es sehr billig gegangen ist in die Herstellung neuer (unveränderlich) Kopien von ihnen mit leichten Veränderungen zu schaffen. Java HashMaps sind nicht dazu gedacht, auf die gleiche Art und Weise zu arbeiten. Sie müssten, um sie jedes Mal klonen Sie eine Änderung vornehmen, was teuer sein kann.
Wenn Sie wirklich wollen aus Java-Mutation-Land zu bekommen (zB vielleicht sind Sie diese HashMaps um für eine lange Zeit zu halten und nicht wollen, dass Java überall nennt, oder Sie brauchen, um sie über print
serialisiert und read
, oder Sie wollen mit ihnen in einem Thread-sichere Art und Weise mit der Clojure STM) Sie zwischen Java HashMaps und Clojure Hash-Karten leicht genug, denn Clojure Datenstrukturen implementieren die richtigen Java-Schnittstellen umwandeln kann arbeiten, damit sie miteinander reden können .
user> (java.util.HashMap. {:foo :bar})
#<HashMap {:foo=:bar}>
user> (into {} (java.util.HashMap. {:foo :bar}))
{:foo :bar}
Wenn Sie eine do
artige Sache wollen, die das Objekt zurückgibt Sie arbeiten, wenn Sie auf es getan arbeiten, können Sie doto
verwenden. In der Tat ist ein Java HashMap wie das Beispiel in der offiziellen Dokumentation für diese Funktion verwendet, was ein weiterer Hinweis darauf, dass es nicht das Ende der Welt ist, wenn Sie Java-Objekte verwenden (umsichtig).
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))
Einige mögliche Strategien:
-
Beschränken Sie Ihre Mutation und Nebenwirkungen auf eine einzelne Funktion, wenn Sie können. Wenn Ihre Funktion immer den gleichen Wert zurückgibt die gleichen Eingänge gegeben, kann es tun, was es intern will. Manchmal mutiert ein Array oder Karte ist der effizienteste oder einfachste Weg, einen Algorithmus zu implementieren. Sie werden immer noch die Vorteile der funktionalen Programmierung genießen, solange Sie nicht „Leck“ Nebenwirkungen auf den Rest der Welt.
-
Wenn Sie Ihre Objekte um für eine Weile sein werden oder sie müssen gut mit anderen Clojure Code spielen, versuchen, sie in Clojure Datenstrukturen so schnell wie möglich, und wirft sie in Java HashMaps zurück an die letzte Sekunde.
(wenn sie zurück zu Java Fütterung)
Es ist völlig in Ordnung, die Java-Hash-Map auf herkömmliche Weise zu verwenden.
(do (. m put "key" "value") m)
This is not idiomatic Clojure code, plus I'm modifying m instead of creating a new map.
Sie ändern eine Datenstruktur, die tatsächlich geändert werden soll. Der Hash-Map von Java fehlt die strukturelle gemeinsame Nutzung Dadurch können Clojures-Karten effizient kopiert werden.Der im Allgemeinen idiomatische Weg, dies zu tun, besteht darin, Java-Interop-Funktionen zu verwenden, um mit den Java-Strukturen auf die typische Java-Art zu arbeiten, oder sie sauber in Clojure-Strukturen umzuwandeln und mit ihnen auf die funktionale Clojure-Art zu arbeiten.Es sei denn natürlich, es macht das Leben einfacher und führt zu besserem Code.dann sind alle Wetten ungültig.
Dies ist ein Code I Hashmaps schrieb mit, als ich versuchte Speichereigenschaften der clojure Version vs Java (aber von clojure verwendet) zu vergleichen
(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))
Dies ist eine Sammlung zu nehmen und zurück, wie oft jede andere Sache (sagen wir ein Wort in einem String) wiederverwendet wird.