CLOVER🍀

That was when it all began.

ClojureとJavaのコレクションを渡り歩く

ちょっと試してみただけの、小ネタです。

ClojureJavaのコレクションの相互運用的な。List、Set、Mapについてですね。

まずは、ClojureJavaのコレクションを作成して、普通に使えるのかという話から。

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

なんとか相互には変換できそうなので、まあいいかなと。