積読のまま第2版を買ったので、現在手元にそれぞれ1冊ずつある状況なのだけど……。
- 作者: Stuart Halloway,川合史朗
- 出版社/メーカー: オーム社
- 発売日: 2010/01/26
- メディア: 単行本(ソフトカバー)
- 購入: 10人 クリック: 338回
- この商品を含むブログ (72件) を見る
- 作者: Stuart Halloway and Aaron Bedra,川合史朗
- 出版社/メーカー: オーム社
- 発売日: 2013/04/26
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (11件) を見る
とりあえずメモを残しておく。他にも無くなった項目があるかもしれないけど、そもそも第1版をそこまで読んでいない(何しろ積読だから……)ので問題ない。
パッケージのインポート
一応、パッケージ名付きでも大丈夫。
(new java.util.Random)
しかし毎度パッケージ名付きで書くのは面倒なので、importを使う。
(import java.util.Random) (new Random)
同一パッケージの複数のシンボルをインポートする場合は、リストで書く。
(import '(javax.xml.transform.stream StreamResult StreamSource))
同一パッケージ内の全てのシンボルをインポートする方法は不明。
一度に色々とインポートすることも可能。
(import java.io.File javax.xml.transform.TransformerFactory '(javax.xml.transform.stream StreamResult StreamSource))
特定のネームスペースにインポートしたい場合はnsの:importで。
(ns foo.bar (:import java.io.File javax.xml.transform.TransformerFactory [javax.xml.transform.stream StreamResult StreamSource]))
Clojureのドキュメントによると、nsの:importでインポートしたシンボルは、普通にimportでインポートしたシンボルものより優先されるらしい。
Javaのオブジェクトの生成
基本はnew特殊形式。
; (import java.io.File ; java.util.Random) (new Random) (new File "foo.txt")
構文糖衣で Classname. という形式。
(Random.) (File. "foo.txt")
メソッドやフィールドへのアクセス
基本はドット(.)特殊形式。
(def rnd (Random.)) (. rnd nextInt 10) (. Math PI) (. System exit 0)
インスタンスのメソッド/フィールドだけでなく、クラスのメソッド/フィールド(staticなメソッド/フィールド)でも同じ。
しかし構文糖衣は、インスタンスのメソッド/フィールドとクラスのメソッド/フィールドとで異なる。
(.nextInt rnd 10) Math/PI (System/exit 0)
インスタンスのメソッド/フィールドでは .methodOrFieldName という形式を使い、クラスのメソッド/フィールドでは Classname/membername という形式を使う。クラスのフィールドでは括弧は不要だが、クラスのメソッドでは必要なことに注意。
.methodOrFieldName の形式は、JavaやC++などに慣れた身には違和感があるけど、よく考えればメソッド名がリストの先頭に来るあたりはLisp的な気がする。よく知らないけど、CLOSでも「(method instance)」という順に記述するみたいだ。Classname/membername は――「/」も含めて一つのシンボルとして扱っている感じで、これもLisp的といえばそうなのかも。
間接参照(メソッドチェーン的なもの)
.methodOrFieldName の形式は、間接参照が多段になるとコードが読みにくくなる。例えば『プログラミングClojure』のこのコード。
(.getLocation (.getCodeSource (.getProtectionDomain (.getClass '(1 2)))))
Javaだとこんな感じだろうか?
// list == '(1 2)
list.getClass().getProtectionDomain().getCodeSource().getLocation();
この場合はJavaのコードの方が読みやすいだろう。
ドット(.)特殊形式を使うとJavaのコードに近くなる。
(. (. (. (. '(1 2) getClass) getProtectionDomain) getCodeSource) getLocation)
しかしこれはこれで面倒だ。
そこで構文糖衣として .. マクロが用意されている。
(.. '(1 2) getClass getProtectionDomain getCodeSource getLocation)
随分とスッキリした。
.. はマクロなので、macroexpandやmacroexpand-1で展開した結果を見ることができる。
(macroexpand '(.. '(1 2) getClass getProtectionDomain getCodeSource getLocation)) ; => (. (. (. (. (quote (1 2)) getClass) getProtectionDomain) getCodeSource) getLocation)
ドット(.)特殊形式を使ったコードに展開していることが分かる。
構文糖衣の合わせ技
クラスのメソッド/フィールドとインスタンスのメソッド/フィールドが混在したコードを構文糖衣を使って書くと、例えばこんな感じ。
(.println System/err "foo bar baz") (. System/err println "foo bar baz")
「err」は「System」のstaticなフィールドなので Classname/membername を使い、printlnは「err」の型であるクラス PrintStream のインスタンスメソッドなので .methodOrFieldName の形式ないしドット(.)特殊形式ないしを使う。
ところでドット(.)特殊形式はクラスのメソッド/フィールドとインスタンスのメソッド/フィールドの区別無く使用できるだった。
(. (. System err) println "foo bar baz")
なのでドット(.)特殊形式に展開する .. マクロを使用できる。
(.. System err (println "foo bar baz"))
『プログラミングClojure』(第1版のほう)では、Javaの呼び出しにて積極的に構文糖衣を使うべき、と書いてある。ただ、上記のようにクラスのメソッド/フィールドとインスタンスのメソッド/フィールドが混在する時にどの記法をつかうべきか書かれていない。どのパターンを使えばよいのか判断に悩むところだ。
個人的な書き方
とりあえず、以下の書き方で統一しようと思う。
; インスタンスの生成 (Classname.) ; インスタンスのフィールド/メソッドの呼び出し (.methodOrFieldName instance) (.methodOrFieldName instance argument) ; クラスのフィールド/メソッドの呼び出し Classname/fieldname (Classname/method) (Classname/method argument) ; インスタンスのメソッドのチェーン (.. instance method-1 (method-2 arg) method-3) ; クラスのフィールド + インスタンスのフィールド/メソッド ; クラスのフィールドを 1つのシンボルと見なす (.methodOrFieldName Classname/fieldname) (.methodOrFieldName Classname/fieldname argument) ; 仮にクラスのフィールドの後にインスタンスのメソッドのチェーンがある場合 (.. Classname/fieldname method-1 method-2) (.. Classname/fieldname method-1 (method-2 arg) method-3) ; クラスのメソッド + インスタンスのメソッド ; クラスのメソッドを 1つのシンボルと見なすのは(括弧がつくので)苦しい…… (.. Classname method-1 method-2) (.. Classname method-1 method-2 method-3) (.. Classname (method-1 argument) method-2 method-3) (.. Classname method-1 (method-2 argument) method-3)