Javaとの相互運用の一歩:Java呼び出し

2/3くらい書きかけのまま1か月が経ってしまったので,このままとりあえず晒しておくことにする.後でまとめ直す...

プログラミングClojure

プログラミングClojure

文法の割と枝葉かもしれないが,まずはJavaクラスライブラリの呼び出しから始めよう*1.言うまでもなくJavaクラスライブラリは膨大かつ良く文書化されており,これをClojureがそのまま利用できるのは,例えPerlを相手にしても胸を張れるすばらしい利点である.

これはClojureのまさに核心に位置する機能なので,プログラミングClojureでは早速 第1章 Getting Startedおよび第2章 Clojureひとめぐりで触れられている. ...いるのだが,これが実際の初心者の立場からは,ちと分かりづらい.もちろん第3章 ClojureからJavaを使うというそのものズバリの詳細な章が続くのだが,本書プログラミングClojureでは概要,導入部に当たる第1章,第2章の内容は後の章では前提とみなされ,あまり復習されない.結局,第1章からきちんと頭に入れるべく奮闘することになる.普通の感覚だと「ふーん,雰囲気は分かったよ〜」と流し読みした第1章,第2章に後で何度も立ち戻ることになるかもしれない.まぁ,これはスタイルの問題だけどね.

名前空間

具体的には名前空間がややこしい.これはClojureという言語そのものがこなれてないのが半分,本書の説明が簡素過ぎるのが半分.まず §1.3 Clojureライブラリの探索において

  • import
  • use
  • require

という3つの特殊形式が挙げられている.さて,この節を読んだ後にこれら3つの区別が付くだろうか? せっかく本書には各所に便利な表が掲載されていて分かりやすいのに(例えばマクロのところのquasiquoteやgensym関連の文法一覧),名前空間については表がない.これは非常に残念で,第2版が出るなら改善を要求したい.
名前空間の操作の中でも userequire は機能的に差があるが, import はそもそも対象が異なる. userequireClojure名前空間を操作するが,importJava名前空間を操作する.確かに importJava名前空間の操作に使うのと同一のキーワードなのだが... 各々の差異を把握できもせずにそんな記憶術に思い至るはずもない.

開発規模の増大に応じ,最近の言語ではしばしば名前空間のサポートはかなり複雑化している.しかしClojure名前空間は,その実装の基礎はJavaと同一で,比較的シンプルだ*3.概念的には

  • 全ての変数は何らかの名前空間に属しており,それを明示した記法(Javaで言う「完全修飾名」)が使える.例: java.util.LinkedList
  • ソースコードの各行は何らかの名前空間に属している(いわゆる「今いるところの名前空間 (current namespace)」)
  • 今いるところの名前空間に属する変数は名前空間を明示しなくてもその名前空間の要素として名前解決できる
  • 逆に今いるところの名前空間に属さない変数は(基本的には)名前空間を明示しないと「そんな変数無いよ」となってしまう
  • 今いるところの名前空間に,他の名前空間の名前を追加することができる.Javaの例: デフォルトの名前空間「デフォルトパッケージ」において import java.util.LinkedList; import java.io.*; と書くと,名前空間 java.util に属する名前(クラス名)LinkedList と,名前空間 java.io に属する名前全てが,「デフォルトパッケージ」に追加される.
  • Clojureでも全く同様で,上記の操作がサポートされる.

ここまで書くと,名前空間の概念におなじみの方には「当たり前じゃん」と思われてしまうかもしれない.しかしそれならそれで,他の言語ではもっと複雑な名前空間の操作が行えることを思い出して欲しい.例えば

  • 名前空間の階層化
  • 名前空間に局所的に別名を付ける.C++の例: namespace po = boost::program_options;boost::program_options::options_descriptionpo::options_description として参照できるようになる.
  • あるスコープの中においてのみ,今いるところの名前空間に別の名前空間の名前を追加する.C++の例:
int main(void) {
    for (int i=1; i<=argc; ++i) {
        using namespace std;
        cout << argv[i] << endl;
    }
    return 0;
}

これらはいずれもClojureではサポートされない.それは深い理由と言うよりは実装上の都合だろう.JavaもこれらをいずれもサポートしないのでJVMもこれらをサポートしづらい実装になっている.そこに敢えて複雑化のための層を重ねることを嫌ったのだろう.これらが言語としての複雑さを増すことを補って余りある利点を持っていることは疑いなく,様々な言語で複雑な名前空間の操作がサポートされている*4.ただJavaは,そしてClojureは,そのような選択をしなかったというだけのことだ.
この中で「名前空間の階層化」については説明が必要だろう.「パッケージ java.util.regex はパッケージ java.util の中に含まれており階層構造をなしている」ように見えるからね.ところがこれはそう「見える」ように設計されているだけで,実は2つのパッケージに階層関係は存在しない――調布と田園調布のように.名前空間に含まれる要素(クラス名 Matcher )と名前空間そのもの (java.util.regex) を区切る文字がJavaではピリオドなので,何となくその他のピリオドにも区切りっぽく見えるだけのことだ.
例えば java.lang.reflect.Method*5 というクラスがあるが,2通りのやり方がある import では java.lang.reflect.Method という完全修飾名で使うか,その翻訳単位内に java.lang.reflect 内の名前全てが持ち込まれるか,いずれかとなる.つまり java.lang.reflectjava.lang の「下にある」という意味はほとんど運用上の慣習で,機械レベルではjavalangreflect という長い名前のパッケージと大差ないのだ.
長々と書いたが,これがClojureにおいてメンバ参照および静的メソッド呼び出しで (Math/PI) などとピリオドとスラッシュが入り交じる理由である.

あとで書く: http://lambda-the-ultimate.org/node/3845 Extending the Scope of Syntactic Abstraction by Oscar Waddell and R. Kent Dybvig, POPL '99.

*1:相互と言うからには行ったり来たりしないといけない気もするが,とりあえず呼び出しから.

*2:JSPキター

*3:なおJava名前空間には「パッケージ」という用語が使われる

*4:例えばC++ : Documentation : C++ Language Tutorial : Namespaces, Ruby チュートリアル - 5. クラスとモジュール, Pythonのモジュールインポートのしくみ, C# / CSharp Tutorial >> Language Basics >> Namespace Alias

*5:http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/reflect/Method.html