ちょっと試してみただけの、小ネタです。
ClojureとJavaのコレクションの相互運用的な。List、Set、Mapについてですね。
まずは、ClojureでJavaのコレクションを作成して、普通に使えるのかという話から。
(ns java-collections (:import (java.util ArrayList HashMap HashSet))) (let [alist (doto (ArrayList.) (.add "foo") (.add "bar") (.add "fuga"))] (assert (= (first alist) "foo")) (assert (= (second alist) "bar")) (assert (= (nth alist 2) "fuga")) (assert (= (count alist) 3)) (doall (for [a alist] (println (format "list elm = %s" a))))) (let [hset (doto (HashSet.) (.add "foo") (.add "bar") (.add "fuga"))] (assert (contains? hset "foo")) (assert (contains? hset "bar")) (assert (= (count hset) 3)) (doall (for [a hset] (println (format "set elm = %s" a))))) (let [hmap (doto (HashMap.) (.put "key1" "value1") (.put "key2" "value2") (.put "key3" "value3"))] (assert (= (get hmap "key1") "value1")) (assert (= (try (hmap "key2") (catch ClassCastException e (.toString e))) "java.lang.ClassCastException: java.util.HashMap cannot be cast to clojure.lang.IFn")) (assert (= (sort (keys hmap)) ["key1" "key2" "key3"])) (doall (for [[k v] hmap] (println (format "key = %s, value = %s" k v)))))
まあ、それなりに使えます。ただ、removeみたいに新しいシーケンスを作成するような関数はダメだったり、JavaのMapはClojureの関数ではないので、
(assert (= (try (hmap "key2") (catch ClassCastException e (.toString e))) "java.lang.ClassCastException: java.util.HashMap cannot be cast to clojure.lang.IFn"))
みたいなコードは、普通にコケます。
次は、ClojureのコレクションとJavaのコレクションの合成。Listの場合はconcat、Setの場合はconcat+set、Mapの場合はmergeでOKそうです。
(let [xs '("foo") alist (doto (ArrayList.) (.add "bar") (.add "fuga")) merged (concat xs alist) merged2 (concat alist xs)] (assert (seq? merged)) (assert (seq? merged2)) (assert (instance? clojure.lang.ISeq merged)) (assert (instance? clojure.lang.ISeq merged2)) (assert (= merged '("foo" "bar" "fuga"))) (assert (= merged2 '("bar" "fuga" "foo"))) (assert (= (count merged) 3))) (let [cset #{"foo"} hset (doto (HashSet.) (.add "bar") (.add "fuga")) merged (set (concat cset hset)) merged2 (set (concat hset cset))] (assert (set? merged)) (assert (set? merged2)) (assert (contains? merged "foo")) (assert (instance? clojure.lang.IPersistentSet merged)) (assert (instance? clojure.lang.IPersistentSet merged2))) (let [cmap {"key1" "value1"} hmap (doto (HashMap.) (.put "key2" "value2") (.put "key3" "value3")) ;; ClojureのMap、JavaのMapの順でマージ merged (merge cmap hmap)] ;; 逆はNG (assert (= (try (merge hmap cmap) (catch ClassCastException e (.toString e))) "java.lang.ClassCastException: java.util.HashMap cannot be cast to clojure.lang.IPersistentCollection")) (assert (map? merged)) (assert (instance? clojure.lang.IPersistentMap merged)) (assert (= (sort (keys merged)) ["key1" "key2" "key3"])) (assert (= (sort (vals merged)) ["value1" "value2" "value3"])))
Setの場合、concatだけだとただのseqになっちゃうので、set関数でSetに変換しています。Listの場合は…SeqというかIPersistentCollectionだけどいいかな?気になるなら、apply関数とlistとかvectorで変換すればよいのかと。
Mapの場合はmergeの引数の順を[ClojureのMap JavaのMap]にしないとコケます。consやconcatだとコケなくなりますが、LazySeqになってmap?がfalseを返すようになるので、違うんじゃないかなぁと。
あとは、ClojureのコレクションをJavaのコレクションに変換する場合。これは、ClojureのコレクションはCollectionインターフェースなりMapインタフェースなりを実装しているので、普通に各Javaのクラスのコンストラクタに渡せばよかろうかと。
(let [alist (ArrayList. '("foo" "bar" "fuga"))] (assert (= (first alist) "foo")) (assert (instance? ArrayList alist))) (let [hset (HashSet. #{"foo" "bar" "fuga"})] (assert (.contains hset "foo")) (assert (instance? HashSet hset))) (let [hmap (HashMap. {"key1" "value1" "key2" "value2"})] (assert (= (.get hmap "key1") "value1")) (assert (instance? HashMap hmap)))
なんとか相互には変換できそうなので、まあいいかなと。