25

Mam obiekt java.util.HashMap (wartość zwracana z wywołania kodu Java) i chcę uzyskać nową mapę z dodatkowym para wartości.Clojure: praca z java.util.HashMap w idiomatycznym stylu Clojure

Jeśli m były map Clojure, mogę użyć:

(assoc m "key" "value") 

Ale próbując że na HashMap daje:

java.lang.ClassCastException: java.util.HashMap nie mogą być oddane do clojure.lang.Associative

Brak powodzenia seq albo:

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

java.lang.ClassCastException: clojure.lang.IteratorSeq nie mogą być oddane do clojure.lang.Associative

Jedynym sposobem udało mi się zrobić go było używać HashMap „s własny put , ale zwraca void więc muszę wyraźnie powrócić m:

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

to nie jest idiomatyczne kod Clojure, plus jestem Modi podważając numer m zamiast tworzyć nową mapę.

Jak pracować z HashMap w bardziej Clojure-owski sposób?

+2

Nie sądzę, że to możliwe. Java HashMap nie jest trwałą strukturą danych, więc albo ją modyfikujecie, albo klonujecie i modyfikujecie sklonowaną wersję. – copumpkin

Odpowiedz

30

Clojure sprawia, że ​​zbiory java są w stanie, dzięki czemu można bezpośrednio używać funkcji sekwencji Clojure na java.util.HashMap.

Ale assoc oczekuje clojure.lang.Associative więc musisz najpierw przekonwertować java.util.HashMap do:

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

Edit: prostsze rozwiązanie:

+2

Ale wtedy nie masz hashmapy, masz mapę clojure, która wydaje się, że pokonuje punkt, do którego strzela. – Runevault

+1

So (assoc (do {} m) "key" "value") zamiast (assoc m "key" "value"). Piękny! Dzięki. – foxdonut

+2

Wierzę, że jeśli potrzebujesz ponownie HashMap, możesz po prostu stworzyć nową. (java.util.HashMap. M) –

1

To jest kod napisany przy użyciu hashmap, kiedy próbowałem porównać charakterystykę pamięci wersji clojure z wersją java (ale używaną z cloja u)

(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)) 

Ma to na celu zebranie kolekcji i zwrócenie liczby powtórzeń poszczególnych słów (powiedz słowo w ciągu znaków).

21

Jeśli łączysz się z kodem Java, być może będziesz musiał ugryźć punktor i zrobić to w Javie, używając .put. To niekoniecznie jest grzechem śmiertelnym; Clojure daje ci takie rzeczy, jak i ., dzięki czemu możesz z łatwością pracować z kodem Java.

assoc działa tylko w strukturach danych Clojure, ponieważ wiele pracy zostało wykonane, aby bardzo tanie tworzyć nowe (niezmienne) kopie z niewielkimi zmianami.Java HashMaps nie są przeznaczone do pracy w taki sam sposób. Będziesz musiał je klonować za każdym razem, gdy wprowadzisz zmiany, które mogą być kosztowne.

Jeśli naprawdę chcesz wydostać się z Java mutation-land (np. Być może utrzymujesz te HashMaps przez długi czas i nie chcesz wywoływać Java w dowolnym miejscu, lub musisz serializować je przez print i read, lub chcesz pracować z nimi w sposób bezpieczny dla wątków za pomocą Clojure STM) możesz łatwo przekonwertować między Java HashMaps i Clojure mapami map, ponieważ struktury danych Clojure implementują właściwe interfejsy Java, dzięki czemu mogą rozmawiać ze sobą inny.

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

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

Jeśli chcesz do -Jak rzeczą, która zwraca obiekt, nad którym pracujesz Kiedy skończysz pracę nad nim, można użyć doto. W rzeczywistości, Java HashMap jest używana jako przykład w oficjalnej dokumentacji dla tej funkcji, co jest kolejnym dowodem, że to nie koniec świata, jeśli korzystasz z obiektów Java (rozsądnie).

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)) 

Niektóre możliwe strategie:

  1. Ogranicz mutacji i uboczne efekty do pojedynczej funkcji, jeśli możesz. Jeśli twoja funkcja zawsze zwraca tę samą wartość przy tych samych danych wejściowych, może zrobić to, co chce wewnętrznie. Czasami mutacja tablicy lub mapy jest najskuteczniejszym lub najłatwiejszym sposobem implementacji algorytmu. Nadal będziesz czerpał korzyści z programowania funkcjonalnego, o ile nie "wyciekniesz" efektów ubocznych do reszty świata.

  2. Jeśli twoje obiekty będą działały przez jakiś czas lub muszą ładnie bawić się z innym kodem Clojure, spróbuj jak najszybciej wprowadzić je do struktur danych Clojure i wrzuć je z powrotem do Java HashMaps w ostatniej sekundzie (podczas karmienia ich z powrotem do Javy).

+0

Dzięki za szczegółowe wyjaśnienie. Bardzo przydatne i interesujące. Dowiedziałem się końcówki "doto" zamiast "do", bardziej elegancki sposób wywoływania pustych metod na obiektach Java i przywracania ich na końcu. Można by pomyśleć, że zdaję sobie z tego sprawę, ponieważ czytam książkę Stu ;-) – foxdonut

+0

btw w Clojure 1.7 (2015), i być może niektóre wcześniejsze wersje, 'java.util.HashMap' teraz wypisuje nawet w sposób Clojurely:' (java.util.HashMap. {: A 1: b 2}) 'drukuje w odpowiedzi jako "{: a 1,: b 2}", mimo że nadal jest to 'java.util.HashMap'. na przykład 'assoc' nie będzie działało na tym. – Mars

5

Używanie mapy skrótów Java w tradycyjny sposób jest całkowicie OK.
(do (. m put "key" "value") m)
This is not idiomatic Clojure code, plus I'm modifying m instead of creating a new map.

Modyfikujesz strukturę danych, która naprawdę ma zostać zmodyfikowana. Mapa mieszania Java nie udostępnia udostępniania strukturalnego, która umożliwia wydajne kopiowanie map Clojures. Zasadniczo idiomatycznym sposobem jest użycie funkcji java-interop do pracy ze strukturami java w typowy sposób java lub do czystego przekształcania ich w struktury Clojure i pracy z nimi w funkcjonalny sposób Clojure. O ile oczywiście nie ułatwia życia i daje lepszy kod; wtedy wszystkie zakłady są wyłączone.