元ネタは以下なので、先に参照しておくとわかりやすいと思います。
Java使いをScalaに引き込むサンプル集
同じJVM上で動く言語としてClojureだってあるんだよというのを 知らしめたくて書いてみました。 なおここで示すClojureコードの例はあくまで個人的な書き方なので必ずしも正しい、効率的ではないのでおかしな点があればコメントください。 使ってるバージョンは Clojure 1.2.0 です。
ClojureだってだいたいJavaと同じよu...ゴメンなさい嘘です†
ClojureもJavaの機能をそのまま使うことができます。 以下はFileReaderを使った例。
(import '[java.io File FileReader BufferedReader]) (let [reader (BufferedReader. (FileReader. (File. "temp.txt")))] (try (doseq [line (take-while (comp not nil?) (repeatedly #(.readLine reader)))] (println line)) (finally (.close reader))))
Scalaと違ってJava使いに馴染みやすい記述とは言えないものになっています。 ちなみに with-open マクロを使うと close が不要になります。
(with-open [reader (BufferedReader. (FileReader. (File. "temp.txt")))] (doseq [line (take-while (comp not nil?) (repeatedly #(.readLine reader)))] (println line)))
あとちょっと脱線しますが、 clojure.contrib.io/read-lines を使うともっと楽です。
(use '[clojure.contrib.io :only [read-lines]]) (doseq [line (read-lines "temp.txt")] (println line))
初期化での折り返しは不要ですよ†
Java, Scalaは元記事のとおり。
Java†
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new File("foo.xml"));
Scala†
val factory = DocumentBuilderFactory.newInstance(); val builder = factory.newDocumentBuilder(); val doc = builder.parse(new File("foo.xml"));
Clojureだと以下です。この状況でdefを使うのは変なのでlet使ってます。
(let [factory (DocumentBuilderFactory/newInstance) builder (.newDocumentBuilder factory) doc (.parse builder (File. "foo.xml"))] (do something))
factory, builder を束縛する必要がないなら ..
を使って以下のようにも書けます。
(let [doc (.. (DocumentBuilderFactory/newInstance) (newDocumentBuilder) (parse (File. "foo.xml")))] (do something))
言語の説明記事じゃないので..
の詳細は省きますが、Clojureなら1つ1つ束縛しなくても書けます。
なお Scala にある 別名import はClojureにはありません。(ドキュメントを見る限り)
Listや配列の初期化はClojureでは問題なし†
リストの扱い易さはLisp方言であるClojureの強み。 Common Lisp, Scheme同様に以下で出来ます。
(def ls '("abc" "def" "ghi"))
またClojureにはリストの他にベクターもあります。
(def v ["abc" "def" "ghi"])
ベクターはベクター用の操作関数を利用することで、リストよりも効率よく操作できたりします。
デフォルト引数、あるよ†
元記事にある foo を参考にします。
Scala†
def foo(c1 : Char = 'A', c2 : Char = 'B', c3 : Char = 'C') { println(c1.toString + c2.toString + c3.toString) }
Clojureのcoreだけ使うと以下のようになります。
(defn foo [& {:keys [c1 c2 c3] :or {c1 "A", c2 "B", c3 "C"}}] (println (str c1 c2 c3))) (foo) ; => ABC (foo :c1 "D") ; => DBC (foo :c2 "E" :c3 "F") ; => AEF
1つの引数の場合でも名前を指定しないといけないところがScalaとの違いです。
また keys
や or
でゴチャゴチャしてます。
ここは clojure.contrib.def/defnk
を使うと見やすくなります。
(use '[clojure.contrib.def :only [defnk]]) (defnk foo2 [:c1 "A" :c2 "B" :c3 "C"] (println (str c1 c2 c3)))
throws はClojureだって省略できるよ†
こんな感じで書いても大丈夫です。Clojureも例外処理は書かなくて良いようにできています。
(defn get-connection [] (DriverManager/getConnection "jdbc:sqlite:hoge.sqlite3"))
文字列の比較は罠になりません†
元記事にあった以下の比較
Java†
String str1 = "テスト"; String str2 = "テスト"; String str3 = new String("テスト"); System.out.println(str1 + " == " + str2 + " = " + (str1 == str2)); //=> テスト == テスト = true System.out.println(str1 + " == " + str3 + " = " + (str1 == str3)); //=> テスト == テスト = false
ClojureでもScala同様に値で比較を行います。
(let [str1 "test" str2 "test" str3 (String. "test")] (println str1 "=" str2 "=" (= str1 str2)) ; => true (println str1 "=" str3 "=" (= str1 str3))) ; => true
オブジェクトの比較を行う場合は identical?
が使えます。
(println str1 "identical?" str2 "=" (identical? str1 str2)) (println str1 "identical?" str3 "=" (identical? str1 str3))
Clojureもいろいろ省略できます†
元記事のJavaは以下。
Java†
public int sum(int i1, int i2) { return i1 + i2; }
素直に書いても型、returnが省略されます。
(defn sum [i1 i2] (+ i1 i2))
無名関数を使うと引き数名も省略できます。
(def sum #(+ % %2))
try-catchの共通化だって†
マクロ使えばできます。 ちなみにここでいうマクロはC言語にあるようなマクロやエクセルマクロとはまったくの別物です。
(defmacro trycatch [& body] `(try ~@body (catch Exception e# nil))) (println (trycatch (/ 10 3))) ; => 10/3 Clojureでは分数になります (println (trycatch (/ 10 0))) ; => nil
最初の例で使った with-open
も同様に close するという処理を共通化したマクロです。
ループの入れ子も楽々†
元記事の以下のループ
Java
for (int i = 1; i > 10; i++) for (int j = 1; j > 10; j++) System.out.println(i * j);
Scalaのforみたく、Clojureのforでもできます。
(doseq [x (for [i (range 1 10), j (range 1 10)] (* i j))] (println x))
ただClojureのforは他言語のforと違ってリストを返す関数なので
doseq
でループして出力してあげてます。
またScala同様に入れ子はいくつでも可能です。
(doseq [x (for [i (range 1 4), j (range 1 4), k (range 1 4), l (range 1 4)] (* i j k l))] (println x))
またfor内での条件も同様
(doseq [x (for [i (range 1 10), j (range 1 10) :when (or (even? i) (even? j))] (* i j))] (println x))
リストへの変換はもともとリストを返してるのでもちろんできます。
リスト操作なら任せろ†
ClojureはLisp方言の1つなのでリスト操作はもちろん得意
(let [ls '(1 2 3 5 3 5 7 8)] ; ユニーク (println (distinct ls)) ; 偶数奇数でのグループ分け (println (group-by odd? ls)) ; 和 (println (apply + ls)) ; 積 (println (apply * ls)) ; それぞれ2倍したリスト (println (map #(* % 2) ls)))
2つのリストの差分は core で上手い方法が見つからなかった。。 とりあえずセットを使うと以下のように書けます。
(use '[clojure.set :only [difference]]) (println (difference (hash-set 1 2 3 4 5) (hash-set 1 4 5)))
またClojureでもScala同様にArrayListなどは扱うことができます。
(import '[java.util ArrayList]) (let [ls (ArrayList.)] (doto ls (.add 1) (.add 2) (.add 3)) (println (apply + ls)) (println (map #(str "x=" %) ls)))
あとがき†
ぜんぜんたいしたコードではないですが、ClojureでもScala並に短く書けることはわかってもらえたと思います。 見た目のわかりやすさといった点では括弧が大きな壁にはなると思いますが、 まぁこんな言語もあるんだよというのが伝われば幸いかなぁと思います。