Unrestricted closure

本連載では過去数回に渡って、Java SE 7で導入される予定となっているJavaのクロージャについて、Closures for the Java Programming Languageで公開されているプロトタイプの仕様や実装をベースに解説している。プロトタイプ実装は逐次更新されており、仕様との差異も生じているので注意してほしい。たとえばエンクロージャのローカル変数に指定する@Sharedアノテーションや今回紹介するUnrestricted closureの「==>」などはまだ仕様には掲載されていない。これらは今後の仕様のアップデートにおいて反映させるとのことだ。

BGGA版の仕様では、通常のクロージャとは別に「Unrestricted closure」と呼ばれるものが定義されている。Unrestricted closureではcontinueやbreakで繰り返し処理を抜けたり、returnでエンクロージャの処理を終了したりすることが可能。Unrestricted closureは「=>」ではなく「==>」を用いて定義する。

たとえばリスト1のようなコードの場合、sample()メソッドの第1引数に渡された値が0だったらreturnで処理を抜ける。その結果、3回目のsample()呼び出しは実行されない。なおUnrestricted closureは実装が完了したばかりのため、最新版の実装でなければこのコードはコンパイルできない。

リスト1 UnrestrictedClosure.java - Unrestricted closureの例

class UnrestrictedClosure {
    static void sample(int number, { int ==> void } block) {
        block.invoke(number);
    }

    public static void main(String[] args) {
        { int ==> void } unrestricted = 
            { int num ==> 
                 if (num == 0) return;
                 System.out.println(num); };

        sample(10, unrestricted);
        sample(0, unrestricted);
        sample(20, unrestricted);
    }
}

もし上記のクロージャを通常通り「=>」で宣言した場合(すなわちUnrestrictedにしなかった場合)、次のようなコンパイルエラーになるはずだ。

プロンプト1 通常のクロージャではreturnできない

UnrestrictedClosure.java:5: 'return' not allowed in a restricted closure
                 if (num == 0) return;
                               ^

Control invocation syntax

BGGAのクロージャには「Control invocation syntax」と呼ばれるシンタックスシュガーが用意されている。これは、クロージャを引数に取るようなメソッドを簡潔に呼び出せるようにするためのものである。Control invocation syntaxは、最後の引数がUnrestricted closureであるメソッドの呼び出しに対して適用できる。

たとえば、リスト2に示すようなメソッドを定義したとする。このメソッドは唯一の引数(すなわち最後の引数)としてUnrestricted closureを受け取る。

リスト2 最後の引数がUnrestricted closureであるメソッドの例

    static void sample({ ==> void } block) {
        block.invoke();
    }

通常の方法でこのメソッドを呼び出す場合はリスト3のようになるだろう。

リスト3 通常の呼び出し

        sample({ ==> System.out.println("Hello!"); });

Control invocation syntaxを利用することで、この呼び出しをリスト4のように記述することができる。

リスト4 Control invocation syntaxを利用した呼び出し

        sample() {
            System.out.println("Hello!");
        }

なおメソッドが複数の引数を受け取る場合には、クロージャである最後の引数以外はリスト5のような具合に通常通り記述すればよい。

リスト5 クロージャ以外に引数がある場合

    static void sample(int x, int y, { ==> void } block) {
        block.invoke();
    }

    sample(10, 20) {
        System.out.println("Hello!");
    }

Unrestricted closureが引数を受け取る場合には、まずクロージャ側の仮引数を列挙し、その後ろにコロンで区切ってメソッドに渡す値を指定する。リスト6はリスト1のsample()メソッドをControl invocation syntaxを利用して呼び出した例である。この例の場合、sample()メソッドには0が渡される。

リスト6 クロージャが引数を取る場合の呼び出し

class ControlInvocation3 {
    static void sample(int number, { int ==> void } block) {
        block.invoke(number);
    }

    public static void main(String[] args) {
        sample(int num: 0) {
            if (num == 0) return;
            System.out.println(num);
        }
    }
}

新しい制御構文の定義

Unrestricted closureとControl invocation syntaxを活用すれば、Javaの言語仕様にはない独自の制御構文をメソッドとして定義することも可能となる。具体的には、制御の対象となるコードをクロージャとして(最後の引数で)渡すメソッドを用意し、そのメソッド内で制御コードと共にinvokeが実行されるようにしておく。するとメソッドの呼び出し元ではControl invocation syntaxによってクロージャ部分がステートメントとして記述できるので、forやwhileなどの制御構文と同様の使い勝手が実現できるというわけだ。

BGGA版の仕様ではその具体例がいくつか掲載されている。例えば指定されたロック機構で排他処理を行うwithLockだ。withLockメソッドの定義はリスト7のようになる。

このwithLockメソッドは第2引数で渡されたクロージャのinvoke()呼び出しの前に、第1引数で渡されたjava.util.concurrent.locks.Lockオブジェクトを用いてロックを取得する。そしてinvoke()の処理が終了したらロックの開放を行う。

リスト7 withLockの定義例

    public static  T 
            withLock(Lock lock, { ==>T throws E } block) throws E {
        lock.lock();
        try {
            return block.invoke();
        } finally {
            lock.unlock();
        }
    }

これを利用するにはControl invocation syntaxを利用してリスト8のように記述すればよい。実際にはSystem.out.printlnの部分がクロージャとして渡され、withLockメソッド内のinvoke()呼び出しによって実行されるため、この処理は指定されたLockオブジェクトによって排他制御されることになる。

リスト8 withLockを利用して排他処理を実行

    Lock lock = new ReentrantLock();
    withLock(lock) {
        System.out.println("Under 'withLock' control.");
    }

なお、このwithLockメソッドのControl invocation syntaxを利用しない場合の呼び出しはリスト9のようになる。

リスト9 Control invocation syntaxを利用しない場合

    withLock(lock, 
             { ==> 
                 System.out.println("Under 'withLock' control.");
             });