Groovy 入門

2012-08-27 改訂, 2013-01-10 リンク更新 (鈴)
2012-02-03 時点のページ
2009-04-16 時点のページ

1. はじめに

Groovy [codehaus.org] は主に Ruby [ruby-lang.org] に強くインスパイアされた Java の一方言 である。 その処理系は標準的な Java VM 上で動作し,標準的な Java 言語によるプログラムと同じバイナリ形式 (class ファイル) をとり, 両者を透過的に組み合わせることができる。 Groovy は既存の Java システムとの高度な互換性と,Python や Ruby に代表される,いわゆる軽量言語の利点を両立させている。

本稿は,Java の知識を前提として Groovy 言語の概要を紹介する。 Ruby や Python の知識は必須ではないが,知っていれば,より Groovy を理解しやすいだろう。

かつて不評だった実行速度も改善された今,Groovy の最大の弱点は,センスに疑問があるその長い名前です。 今までの歴史によれば,長い名前のプログラミング言語が成功するには,その分野に競合する言語がないなどの幸運が必要です。 Fortran はそうした幸運な例でした。 さまざまな軽量言語が覇を競っている現在,その渦中にあって,次章で説明するように Groovy は言語名だけでなくコマンド名も, さらに悪いことに (よく言語の略称になる) 標準のファイル接尾辞も同じように長く, 自ら不人気の道を邁(まい)進しようとしている観があります。

実際,"groovy" という名前は,典型的な Unix 環境では見かけの長さ以上に悪い選択です。 例えば pyt で始まるコマンドは普通ほかにありませんから,同じだけ長い名前ながら,このつづりで 始まる python は,はじめの3文字を打鍵して補完するだけで起動できます。 しかるに groovy は groupsgroff と区別するために groo まで打鍵して補完しなければなりません。 毎回フルスペルを打鍵するのと五十歩百歩です。 対話シェルを動かそうと思ったら,いったん groovy まで打鍵または補完展開した後,さらに sh と打鍵する必要があります。ほとんどいやがらせの域です。:-( あくまでこの名前にこだわるならば,JavaScript に対する js のように短い略称を本家筋で一つだけ用意しておくべきだったでしょう)

またこの単語からは,これが Java の1方言であり,Java をかじった人なら即座に書き始めることができ,既存の Java システムに簡単に組み入れることができる,熟考された良い意味での大人の言語であることが伝わってきません。 自らを groovy (イカす,かっこいい,キマッてる) と名乗る行為からは,どちらかといえば,その反対の (あえて言えば,いわゆる中二的な) 印象を受けます。

それでもこのチュートリアルを書いて Groovy への入門を君にすすめているのは,それに見合うだけの良さが,この言語と処理系にあるからです。 ほとんど失敗に近い名前のことは,たとえそれで操作にイライラさせられるとしても,印象が良くないとしても,しばらく我慢してください。

2. てほどきとインストール

ここでは Unix 環境 (Mac OS X) での操作を説明する。 純粋に Java で作られたプログラムだから,他のプラットフォームでも基本は変わらない。

2.1 対話シェルを動かそう

Groovy - Download [codehaus.org] から Groovy の Binary Release をダウンロードしよう。 執筆時現在の最新安定版は 2012年 7月 25日にリリースされた Groovy 2.0.1 の groovy-binary-2.0.1.zip である。

Windows ならば上記と同じ場所で配布されている Windows-Installer: Binary Release の groovy-2.0.1-installer.exe をダウンロードして実行するのが便利で簡単です。 Gant [codehaus.org] や GPars [codehaus.org] など多くのツールやモジュールもまとめてインストールできます

この groovy-binary-2.0.1.zip を展開すると groovy-2.0.1 というディレクトリが作られる。 その下の bin ディレクトリに移動する。 そこには Groovy のインタープリタやコンパイラの起動スクリプトがある。 とりあえず groovysh (名前の意味は "groovy shell") を起動しよう。 これは Groovy 言語を対話的に解釈し,実行する対話シェル (interactive shell) である。

01:~/tmp$ unzip ~/Downloads/groovy-binary-2.0.1.zip
Archive:  /Users/suzuki/Downloads/groovy-binary-2.0.1.zip
   creating: groovy-2.0.1/
  inflating: groovy-2.0.1/LICENSE.txt  
  inflating: groovy-2.0.1/NOTICE.txt  
   creating: groovy-2.0.1/lib/
  inflating: groovy-2.0.1/lib/groovy-2.0.1.jar  
  inflating: groovy-2.0.1/lib/commons-cli-1.2.jar  
  inflating: groovy-2.0.1/lib/xstream-1.4.2.jar  
  inflating: groovy-2.0.1/lib/jline-1.0.jar  
  ……途中略……
   creating: groovy-2.0.1/bin/
  inflating: groovy-2.0.1/bin/groovy.icns  
  inflating: groovy-2.0.1/bin/groovysh.bat  
  inflating: groovy-2.0.1/bin/groovyc  
  inflating: groovy-2.0.1/bin/groovyConsole  
  inflating: groovy-2.0.1/bin/groovyConsole.bat  
  inflating: groovy-2.0.1/bin/groovydoc.bat  
  inflating: groovy-2.0.1/bin/groovy.bat  
  inflating: groovy-2.0.1/bin/startGroovy.bat  
  inflating: groovy-2.0.1/bin/java2groovy.bat  
  inflating: groovy-2.0.1/bin/groovysh  
  inflating: groovy-2.0.1/bin/startGroovy  
  inflating: groovy-2.0.1/bin/groovy  
  inflating: groovy-2.0.1/bin/grape.bat  
  inflating: groovy-2.0.1/bin/groovyc.bat  
  inflating: groovy-2.0.1/bin/groovydoc  
  inflating: groovy-2.0.1/bin/grape  
  inflating: groovy-2.0.1/bin/java2groovy  
   creating: groovy-2.0.1/embeddable/
  inflating: groovy-2.0.1/embeddable/groovy-all-2.0.1.jar  
  inflating: groovy-2.0.1/embeddable/groovy-all-2.0.1-indy.jar  
  inflating: groovy-2.0.1/JSR223-LICENSE.txt  
  inflating: groovy-2.0.1/ASM-LICENSE.txt  
  inflating: groovy-2.0.1/ANTLR-LICENSE.txt  
  inflating: groovy-2.0.1/CLI-LICENSE.txt  
   creating: groovy-2.0.1/META-INF/
01:~/tmp$ cd groovy-2.0.1/bin
01:~/tmp/groovy-2.0.1/bin$ ./groovysh
Groovy Shell (2.0.1, JVM: 1.6.0_33)
Type 'help' or '\h' for help.
-------------------------------------------------------------------------------
groovy:000> 
Apple が用意する Mac OS X 用の Java 1.6.0_33 は無指定時に Shift JIS を使います。 ターミナル上で UTF-8 を使わせるには ~/.bash_profile で
export _JAVA_OPTIONS="-Dfile.encoding=UTF-8"
と設定します (ターミナルに限らず,より一般にこの設定で Java を動かすためには ~/.MacOSX/environment.plist を使います。 ただし,この方法は新しい OS X 10.8 "Mountain Lion" では無効になっています。 かわりに /etc/launchd.conf を使うことができます。 man ページの launchd.conf(5) と launchctl(1) を参照してください。 さしあたりはここで説明した ~/.bash_profile の設定で十分です)
この設定をすると,Java を実行するたびに "Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8" が表示されます。 groovysh を起動すると,実際には Groovy Shell のタイトル表示に先立ってこれが表示されます。
01:~/tmp/groovy-2.0.1/bin$ ./groovysh
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
Groovy Shell (2.0.1, JVM: 1.6.0_33)
煩雑ですから,このチュートリアルではいちいちこの表示を書かないことにします。

Oracle が配布する Mac OS X 用の JDK 7u6 (1.7.0_06) ではこのように環境変数 _JAVA_OPTIONS を設定する必要はありません。 素で UTF-8 が使われます。

対話シェルでは Groovy 言語をその場で入力して応答を得ることができる。

groovy:000> 2 + 3
===> 5
groovy:000> "hello, world"
===> hello, world
groovy:000> println "hello, world"
hello, world
===> null
groovy:000> println()

===> null
groovy:000> exit
01:~/tmp$  

この例では,

  1. 算術式 2 + 3 を与えて,結果の数 5 を得,
  2. 文字列 "hello, world" を与えて,結果の文字列 "hello, world" (ただしここでは引用符は表示されていない) を得,
  3. 改行付き印字メソッド println を呼び出して引数を画面に表示させ改行させた後, 結果の戻り値 null を得,
  4. 再び println を今度は無引数で呼び出して単に改行させた後,結果の戻り値としてまた null を得ている。

これらの入力はどれも Groovy の式であり,===> の先に表示されているのは式を評価した結果の値である。 このように対話シェルは,式を与えて結果の値を得る電卓として使うことができる。 文字列定数を評価した結果の値は,その文字列定数自身である。 メソッド呼出しの結果の値は,そのメソッドの戻り値である。 印字メソッド println による画面表示はあくまでメソッド内の動作であり,結果の値は null である。

文字列定数,メソッド,引数,null などの言葉の意味は Java と同じと思ってください。 注意が必要な Java との相違点については,第3章にまとめました。 println とはどのクラスのメソッドなのか,については 4.3 節で扱います。 さしあたり,それまでは,なぜかどこでも使えるメソッドだ,ぐらいに思ってください。 メソッドですから this.println() などとしても OK ですが,普通は誰もそうしません。 その理由は 4.3.1 節で述べます。

Groovy 言語では,他の式の一部分ではない式,つまり最外の式に限り,1個以上の引数付きのメソッド呼出しの丸括弧を省略してよい。 ここでは println の最初の呼出しで丸括弧を省略している。 その次の呼出しは無引数だから丸括弧を省略することはできない。

最初の呼出しを省略せずに書くと println("hello, world") です。 これを対話シェルに与えて,println "hello, world" と同じになることを確かめてください。

groovysh は Groovy 言語の範囲外の「コマンド」をいくつか用意している。 exitgroovysh 自身を終了させるコマンドである。 メソッドではないから丸括弧を付けずに exit と入力する。 help と入力すれば全コマンドを一覧できる。

Ruby ファンへの警告: メソッド呼出しの丸括弧を省略できるのは,1個以上の引数を伴うときだけです。 もしも無引数呼出しで丸括弧を省略すると,コマンドとして解釈できればコマンドとして,そうでなければプロパティやフィールド (4.4 節) として解釈されます。
ですから,もしも println() のつもりで println とだけ入力すると, No such property (そのようなプロパティはない) と叱られます。

対話シェルはコマンドと Groovy の式のほか,Groovy の文や宣言も受け付ける。 文や宣言にも結果の値がある。

最外の式はそれ自身,式文 (expression statement) と見なせますから,結局,対話シェルで入力できるのは, コマンドと Groovy (≒ Java) のおよび宣言ということになります。 文へと拡大適用した「結果の値」の概念は 4.1 節で説明します。

一応 Groovy が動くことを確かめたら,きっちりインストールしよう。

注意深い人は,この時点で自分のホーム・ディレクトリに ~/.groovy というディレクトリが作られていることに気付いたかもしれません。 このディレクトリには groovysh.history というファイルが作られています。 これは対話シェル groovysh が入力行を記録したテキスト・ファイルです。 これにより,次回また対話シェルを使うとき,(Emacs と同じく) 上向き矢印キーや Control-P の打鍵ですぐに前回の入力を取り戻せます。
この ~/.groovy の直下に自分で lib というディレクトリを作り,そこに jar ファイルを置くと,CLASSPATH をいちいち通さなくても (groovysh に限らず) Groovy 全般から使えるようになります。 まだ今は必要ないと思いますが,ここに Groovy から使いたい自分用の定番ライブラリの jar ファイルを置くと便利です。

2.2 インストールしよう

前節では,何が展開されたか確認の意味も兼ねて groovy-2.0.1/bin のディレクトリまで移動した。 実際には,わざわざそうしなくても groovysh への相対パスまたは完全パスを与えることで groovysh を起動できる。 PATH の通った場所からシンボリック・リンクを張っただけも起動できる。 最後の方法が最も手間がかからない。ここでは,その方法を使ってインストールしよう。

下記は /usr/local/sharegroovy-2.0.1 を展開し, /usr/local/bin から groovysh へのシンボリック・リンクを張る例である。

01:~/tmp$ cd /usr/local/share
01:/usr/local/share$ sudo unzip ~/Downloads/groovy-binary-2.0.1.zip
……出力略……
01:/usr/local/share$ cd ../bin
01:/usr/local/bin$ sudo ln -s ../share/groovy-2.0.1/bin/groovysh .

非対話型インタープリタ groovy,コンパイラ groovyc,簡易統合開発環境 groovyConsole のシンボリック・リンクも同様に作ろう。

01:/usr/local/bin$ sudo ln -s ../share/groovy-2.0.1/bin/groovy .
01:/usr/local/bin$ sudo ln -s ../share/groovy-2.0.1/bin/groovyc .
01:/usr/local/bin$ sudo ln -s ../share/groovy-2.0.1/bin/groovyConsole .

こうした後,type コマンドと ls コマンドで次のように出力される。

01:~/tmp$ type groovysh
groovysh is /usr/local/bin/groovysh
01:~/tmp$ ls -l /usr/local/bin/groovysh
lrwxr-xr-x  1 root  admin  34 Aug  8 10:08 /usr/local/bin/groovysh -> ../share/g
roovy-2.0.1/bin/groovysh
01:~/tmp$  

groovygroovycgroovyConsole についても同様に確かめよう。

コマンド名の解釈を確認する方法として type ではなく which を使うことを学んでいるかもしれません。 zsh ならばそれもありですが, bash ならばその方法は忘れてください。 入力されたコマンドをシェルがどう解釈するのか,正確な答えはシェル自身が知っています。 しかし bash にとって which は外部のコマンド /usr/bin/which であり,which の答えはあくまで外部からの推定値です。

type name と type -a name (可能な候補をすべて挙げる) を使うことをおぼえてください。

2.3 Groovy スクリプトを動かしてみよう

下記の内容のファイル hello.groovy をテキスト・エディタで作ろう。

println "hello, world"
ただし,Emacs ファンならば,その前に 付録 1 を見るべきです。;-)

コマンド groovy を使えば,これを下記のように実行できる。

01:~/tmp$ groovy hello.groovy
hello, world
01:~/tmp$  

または下記の内容のファイル Test.groovy をテキスト・エディタで作ろう。

public class Test
{
  public static void main(String[] args) {
    System.out.println("hello, world");
  }
}

これを下記のように実行できる。

01:~/tmp$ groovy Test.groovy
hello, world
01:~/tmp$  

2.4 Groovy コンパイラを使ってみよう

前節で作ったファイル Test.groovygroovyc (名前の意味は "groovy compiler") でコンパイルしよう。

01:~/tmp$ groovyc Test.groovy
01:~/tmp$  

このとき,同じディレクトリに Test.class ファイルが作られている。 CLASSPATH が通っている限り,この Test.class ファイルの Test クラスは groovysh や他の Groovy スクリプトから普通の Java クラスと同じように利用できる。

groovy:000> Test.main(null)
hello, world
===> null
groovy:000>  

あるいは groovy-2.0.1/embeddable/groovy-all-2.0.1.jar を CLASSPATH に含めることにより,java コマンドで直接実行することもできる。この jar ファイルは groovy の処理系一式のクラス・ファイルを含んでいる。

01:~/tmp$ java -cp ".:/usr/local/share/groovy-2.0.1/embeddable/groovy-all-2.0.1.
jar" Test
hello, world
01:~/tmp$  

2.4.1 Java から Groovy を呼び出してみよう

下記の内容のファイル Greeting.groovy をテキスト・エディタで作ろう。

// テスト用クラス
package com.example.proj

public class Greeting
{
    public static void hello() {
        println "hello, world"
    }
}

これを groovyc でコンパイルすると,Java クラス・ファイル com/example/proj/Greeting.class が得られる。

01:~/tmp$ groovyc Greeting.groovy
01:~/tmp$  

これを Java プログラムから呼び出そう。

まず,下記の内容のファイル Main.java をテキスト・エディタで作る。

import com.example.proj.Greeting;

public class Main
{
    public static void main(String[] args) {
        Greeting.hello();
    }
}

次に,Java コンパイラ javaMain.java をコンパイルする。

01:~/tmp$ javac Main.java
01:~/tmp$  

最後に,カレント・ディレクトリと groovy-2.0.1/embeddable/groovy-all-2.0.1.jar を CLASSPATH に含めて javaMain クラスを実行する。

01:~/tmp$ java -cp ".:/usr/local/share/groovy-2.0.1/embeddable/groovy-all-2.0.1.
jar" Main
hello, world
01:~/tmp$  

Java で書かれた Main クラスの main メソッドが,Groovy で書かれた com.example.proj.Greeting クラスの hello メソッドを呼び出し,hello メソッドが hello world を表示する。

Groovy で書かれたクラスは,実行時に groovy-all-2.0.1.jar を必要とする以外,普通の Java クラスと区別せずに Java から使うことができる。

2.5 簡単な統合開発環境: groovyConsole

groovyConsole2.3 節 のようなスクリプトの編集と実行をまとめて行える Swing ベースの GUI プログラムである。

01:~/tmp$ groovyConsole &
[1] 974
01:~/tmp$  

ちなみに,上図のように Groovy スクリプトでは (いわゆるスクリプトらしい) はだかの文や変数宣言を,Java とまるで変わらないクラス定義と混ぜて書くことができる。 もちろん,groovyConsole に限らず 2.3 節 で説明した groovy コマンドでも話は同じである。

注意: このように混ぜて書くときは,クラス名をスクリプトのファイル名と同じにしないでください。 実際,上記のようなスクリプトのファイル名を Foo.groovy として実行すると, "Invalid duplicate class definition of class Foo" のようなエラーになります。 6.2 節 で説明します。

2.6 ここまでと,これから

この章で君は groovy 処理系の基本的な使い方を学んだ。 groovy は対話的に実行したり,スクリプト・ファイルを実行したり, Java のクラス・ファイルへとコンパイルできる。

一方,ここまでのプログラム例に,セミコロンを省略したり,System.out なしに println メソッドで画面表示したりと,微妙に Java と異なる点があったことに戸惑っているかもしれない。 しかし,それはまだ気に病むことではない。 もしもそうしたければ Java と全く同じようにセミコロンを書いたり,System.out.println を呼び出すことができる。

これから,まず 第3章で,Java との共通性に注目して Groovy を学ぼう。 Groovy らしくはないかもしれない。 しかし,これだけおぼえれば,ただちに正しく動く Groovy プログラムをどんどん書くことができる。 もちろん,それだけでは Groovy は対話的に動かすことのできる Java の少し変わった方言にすぎない。

だから,次に 第4章で,軽量言語,ないしはスクリプト言語としての Groovy の書きかたを学ぼう。 セミコロンを省略できることはその一部である。 おそらく,君は今まで断片的な Groovy の解説記事をあちこちで見てきたことだろう。 そこでは,まるで Ruby や Python のように def キーワードでメソッドを定義していたかもしれない。 そういったスクリプト言語としての Groovy の姿がここで出現する。

さらに章を改めて 第5章 では,最近,様々な言語で抽象化手段として注目されているクロージャ (closure) について学ぼう。 クロージャそのものは Algol 60 にも萌芽が見られる古典的な構成要素だが,Groovy はとりわけ Ruby でのクロージャの活用法にインスパイアされている。 5.5 節 の例にあるように,表面的には Ruby の模造品にしか見えないかもしれない。 しかし,その背後にある構文と意味論は Ruby の引き写しではない。複雑さを省き,簡明なかたちに Java の体系を再構成している。 ある意味,ここに Groovy らしさの真髄がある。

しかし,Java と共通するクラス定義を維持しながらスクリプトとしてのプログラム構成を実現するために Groovy はある種の暗黒面をかかえている。 Groovy を本当に理解するためにはこれを避けることはできない。 最後に 第6章でこれについて見ておこう。

3.二十箇条 Groovy 速成講座

もしも君が Java を知っているならば,数千行以上の Groovy プログラムを書くために十分なだけの知識を得るのにそれほど時間はいらない。

  1. 妥当な Java プログラムの大半はそのまま妥当な Groovy プログラムである。
    したがって Java プログラマならば,誰でもすぐに Groovy プログラミングが可能です。
  2. どの Java クラス・ライブラリも Groovy から利用できる。 Java と同じく,環境変数 CLASSPATH に入れて参照可能にできる。
    下記はあらかじめ import 済みです。陽に import しなくても構いません。
    java.io.*
    java.lang.*
    java.math.BigDecimal
    java.math.BigInteger
    java.net.*
    java.util.*
    groovy.lang.*
    groovy.util.*
    ホーム・ディレクトリ直下に ~/.groovy/lib/ を作ってそこに jar ファイルを置くと,CLASSPATH の設定なしに参照できます。
  3. ファイルの接尾辞は .groovy である。 このことを除き, 妥当な Java プログラムのファイル編成は,そのまま妥当な Groovy プログラムのファイル編成である。
    例えば package com.example.proj と宣言されたクラス Foo のソースを, ディレクトリ com/example/proj にファイル Foo.groovy として置けば, 他の Groovy プログラムから import com.example.proj.Foo のように参照できます。

    ソース・ファイル Foo.groovy のかわりに,groovyc でコンパイルしたクラス・ファイル Foo.class をそこに置くこともできます。 この場合は普通の Java プログラムからもクラス com.example.proj.Foo を参照できます。 2.4.1 節 を思い出してください。


注意すべき Java との言語仕様の相違点を次に示す。

  1. プリミティブ型と参照型のどちらの場合でも (a == b) は値の一致を判定する。 (a != b) は値の不一致を判定する。
    もしも参照型のアイデンティティの一致を判定するときは a.is(b) を使う。
    Groovy の == 演算とは,数値に対する型合わせの算術変換を伴った equals メソッドです。 つまり,基本的には equals メソッドと同じですが,数値の比較では従来の == と同じように振舞います。

    equals() では 1 と 1.0 は別の値と判定されます。
    groovy:000> new Integer(1).equals(new Integer(1))
    ===> true
    groovy:000> new Integer(1).equals(new Double(1.0))
    ===> false
    
    == では 1 と 1.0 は (整数を実数に変換して比較するので) 同じ値と判定されます。 別々のインスタンスでも == で等しいと判定されます。
    groovy:000> new Integer(1) == new Integer(1)
    ===> true
    groovy:000> new Integer(1) == new Double(1.0)
    ===> true
    groovy:000> 1 == 1.0
    ===> true
    

    new Integer(1) あるいは Integer.valueOf(1) と書く必要は実はありません。 単に 1 と書いたとき,実際には Java の Integer.valueOf(1) を意味します。
  2. charint などのプリミティブ型は,原則として java.lang.Characterjava.lang.Integer などのラッパー・クラスのインスタンスで表現される。 ただし,普段はこのことを意識する必要はない。 プログラムでは普通に charint として宣言して扱うことができる。
    前述のように == (および !=) はアイデンティティではなく値の一致 (および不一致) を判定する演算子として振舞います。他の演算子も,その値に対して,必要ならば算術変換を伴って演算をします。 さらに実装内部に深く立ち入って言えば,可能ならばプリミティブ型のまま (例えば 1 なら Integer インスタンスではなく素の 1 のまま) 演算される場合もあります。 プリミティブ型を扱う Java のクラス・ライブラリとのあいだでは引数と戻り値が透過的に変換されます。 ただし,いずれにせよ普段は意識する必要はありません。
  3. Groovy は null を1個のインスタンスとして扱う。
    したがって式 (null == x) は Java と同じく xnul かどうかを判定する。
    Groovy の null は Java の null と確かに同一ですが, Groovy では擬似的に org.codehaus.groovy.runtime.NullObject の唯一のインスタンス,つまりシングルトンとして扱います。 したがって null.toString()null.getClass() などのメソッド呼出しもできます。
    したがって null.equals(x)null.is(x) は (名目上) それぞれ null と x が同じ値かどうか,同じアイデンティティかどうかを判定します。 (名目は異なりますが) 実際にはどちらも単に x が null かどうかを判定します。
    つまり,Java と同じく (a == b) と書かれた式は,(前述のように Groovy では == が算術変換付き equals だとしても) Groovy でも a が null のときに例外を発生させずに b が null かどうかを判定します。
  4. 文字列定数は単一引用符で囲んでも二重引用符で囲んでもよい。 char 定数を得たいときは文字列から型変換して作る。
    例: (char) 'a' または 'a' as char
    独自の演算子 as を使ったほうが,読むときに Java とは異なる型変換として認識しやすい点でいくらか好ましい。
    二重引用符で囲んだ場合は,Unix のシェルと同じように $ 記号で変数展開ができます。 例: "hello, $name"
    "$a + $b = ${a + b}" のように波括弧で囲むことで式の値を展開できます。
    (型名) によるキャスト演算と as 演算は長さ 2 以上の文字列の char への変換の振舞が違います。 (型名) によるキャスト演算は例外を送出します。 as 演算は最初の文字の char 値を返します。 一般に (型名) によるキャスト演算は,どのように変換すべきか意見の分かれそうな変換に対して,保守的に例外を送出します。
    C# ファンへの警告: Groovy の as は C# の同名の演算子とは似て非なるものです。 "abc" as List のように必ずしも自明ではない変換を大胆に行う場合もあれば, "abc" as ArrayList のようにキャスト失敗の例外を送出する場合もあります。 前者の結果は三つの文字列 (char ではなく!) "a", "b", "c" を要素とする ArrayList になります (直接 ArrayList に変換しようとすると失敗するのに!!)。 C# での (型名) キャストと as の使い分けは Groovy には通用しません。
  5. 任意の式 x(boolean)x のように boolean 型にキャストすることができる。 このキャスト演算は boolean 型が必要とされる箇所では暗黙裏に行われる。 null0, "", 空のコレクション (例: new ArrayList()), 空の配列 (例: new int[0]) は false と見なされる。
    暗黙裏にキャストが行われる典型的な場所は if (E) …while (E) … などのテスト式 E です。

    テスト式 E には任意の型の式を置くことができます。 void 型のメソッド呼出しならば擬似的に null を返すものとして false 扱いされます。 ただし,確かに型は任意ですが,じかに代入演算をおくことは構文的に禁止されています。 もしも本当に代入演算をおきたいときは,さらに括弧で囲みます。

    不可: if (a = foo()) bar();
    可: if ((a = foo())) bar();

    Java では (不適切にも) 許されていた boolean 変数 flag に対する if (flag = true) … が Groovy では (好ましくも) 構文解析時のエラーとなることに注意してください。
  6. protectedprivate によるアクセス制御は,実行時に無視される。 Groovy に対してプライバシーはない。
    Groovy 公式ページ [codehaus.org] は,他のオブジェクトのプライバシーの無視 (disregarding other objects' privacy) を things you can do but better leave undone (できるが,しないほうがよいこと) の一つに挙げています。 単体テスト等での利用に留めるのが妥当です。むしろ,そこでこそ極めて有用な機能になります。 テストのためだけにオブジェクト内部へのアクセス制御をわざわざ緩める必要がなくなるわけです。
    アクセス制御の指定はクラスの情報としてコンパイル時に織り込まれますから, Groovy で定義したクラスを Java からアクセスするときは,この制御が有効になります。
  7. クラス定義,メソッド定義にアクセス制御を指定しなかったとき,public として定義される
    クラスとメソッドに対する Java 本来の無指定時のアクセス制御 (package private) は Groovy では表現できない。
    したがって 2.3 節 の Test.groovy や 2.4.1 節 の Greeting.groovy の public キーワードはすべて省略できます。
  8. クラスのフィールド T abc にアクセス制御を指定しなかったときJava Beans プロパティとしてのゲッター public T getAbc() とセッター public void setAbc(T) およびフィールド private abc自動的に定義される
    フィールドに対する Java 本来の無指定時のアクセス制御 (package private) は Groovy では普通には表現できない。
    実際の Java のコーディングでは,振舞が中途半端で使い方に微妙なセンスが要求されるためか,package private はほとんど使われません。 Java の設計者にしてみれば,気の利いた省略時解釈を用意したはずが,実際にはほとんど使われずに期待外れな結果に終わっているわけです。 Groovy は Java の現実の使われ方を踏まえ,クラスのフィールドの最も普通の定義のしかたを省略時解釈にして,この空白地帯を再利用しています。
    フィールドを package private にしたいときは, groovy.transform.PackageScopeimport して @PackageScope アノテーションを使います。
    import groovy.transform.PackageScope;
    
    class A {
      @PackageScope
      int x;
    
      int y;
    }
    
    この例では java.lang.Object の派生クラスとして public class A が定義されます。 このクラスにはフィールドとして package private な int x と private int y が設けられます。 y に対する JavaBeans プロパティとしてメソッド public int getY() と public void setY(int) が自動的に定義されます。 つまり,Groovy 固有の実装詳細を省略して言えば,上記の Groovy コードは下記の Java コードと等価です。
    public class A
    {
        int x;
        private int y;
    
        public int getY() { return y; }
        public void setY(int value) { y = value; }
    }
    
    クイズ: この Java コードを仮に再び Groovy コードとして解釈したらどうなるでしょうか?

    答: x も y と同じような JavaBeans プロパティとなる。 すなわち,元の x のかわりに private int x フィールドと public int getX() および public void setX(int) メソッドが定義される。

  9. メソッドの throws 宣言は Groovy からは無視される。 チェックされる例外と,チェックされない例外の区別は Groovy にはない。
    アクセス制御の指定と同じく,この宣言もクラスの情報として織り込まれますから,Groovy で定義したクラスを Java からアクセスするときは,この宣言が有効になります。 Groovy で定義したクラスのメソッドから例外を送出して Java で catch する場合は,忘れずに Groovy のメソッド定義で throws 宣言をしておきます。
  10. dowhile 文を使うことはできない。
    そのかわり,無限ループと if 文,break 文の組み合わせで代用できます。
    int i = 1;
    do {
      println i;
      i++;
    } while (i <= 10);
    
    と書くかわりに
    int i = 1;
    for (;;) {
      println i;
      i++;
      if (i > 10) break;
    }
    
    と書きます。
  11. 拡張 for 文では,コロンのかわりに in を使う。 例: for (int i in someList) { … }
  12. 0 個以上の値の並びをカギ括弧で囲んで java.util.ArrayList インスタンスを構築できる。
    配列の初期化構文はない。
    例を示します。拡張 for 文も使ってみます。
    groovy:000> x = [1, 2, 3, 4, 5]
    ===> [1, 2, 3, 4, 5]
    groovy:000> x.getClass()
    ===> class java.util.ArrayList
    groovy:000> for (int i in x) println i
    1
    2
    3
    4
    5
    ===> null
    
    この例では変数 x を突然使っていますが,実は対話シェルと,Groovy スクリプトのクラス定義の外側では,変数を宣言せずに使うことができます。 こうして暗黙のうちに作った変数を Groovy では binding variable と言います。 とりわけ Groovy の対話シェル groovysh では,わざわざ宣言して設けた変数はその1行でしか有効でありませんから,(Ruby の irb のように) 計算した値をちょっとメモするために変数を使うときは宣言せずにいきなり代入します。 詳しくは 6.3 節 以降で説明します 。
  13. クラスによっては,代入時に自動的にキャスト演算が行われる。 java.lang.String 値へのキャストでは toString() メソッドが使われる。 java.util.ArrayList 値から配列変数へのキャストでは toArray メソッドに相当する演算がなされる。
    今の Groovy に配列の初期化構文はありませんが,整数配列 ii を {1, 2, 3} と初期化するには,波括弧をカギ括弧にかえて
    int[] ii = [1, 2, 3];
    
    と書けば OK です。 java.util.ArrayList インスタンスから toArray(int[]) メソッドで int[] オブジェクトが作られます。
  14. 変数やメソッドの型の不一致は,型変換の失敗として実行時に検出される。 メソッドはオブジェクトの動的な型に基づいて実行時にはじめて解決されるから, 未定義メソッドの呼出しも,メソッドの欠如として実行時に検出される。
    例えば,下記のコードは,問題なく groovyc でコンパイルすることができます。
    class A {}
    
    class B {}
    
    class C {
      static void main(String[] args) {
        A a = new B();
      }
    }
    
    しかし,いざ実行してみると,キャストに失敗して例外が発生します。
    groovy:000> C.main(null)
    ERROR org.codehaus.groovy.runtime.typehandling.GroovyCastException:
    Cannot cast object 'B@b4d39c' with class 'B' to class 'A'
            at C.main (a.groovy:7)
            at groovysh_evaluate.run (groovysh_evaluate:2)
            ...
    
    これが型の不一致ではなく,型変換の失敗であることに注意してください。 Groovy は式の値を実装内部で基本的に java.lang.Object 型として扱います。 それを変数に代入するとき,Object から変数の型へとキャストします。 本来の Java ならば静的な型検査の失敗として検出される誤りが Groovy では動的な型変換の失敗になります。

    プログラムの自明な誤りを早期に発見できないという意味で,これは多くのスクリプト言語と共通する Groovy の弱点です。この状況を改善するために Groovy 2.0.0 からオプショナルな静的型検査が導入されました。 下記のように @TypeChecked アノテーションを付けます。
    import groovy.transform.TypeChecked;
    
    class A {}
    
    class B {}
    
    @TypeChecked
    class C {
      static void main(String[] args) {
        A a = new B();
      }
    }
    
    このとき次のようにコンパイル時に型の不一致が検出されます。
    a.groovy: 10: [Static type checking] - Cannot assign value of type
     B to variable of type A @ line 10, column 5.
           A a = new B();
                 ^
    
    1 error
    
    ただし,少なくとも Groovy 2.0.1 の時点では,まだ頼りになる機能とはいいがたいところがあります。 とりわけ クロージャ と組み合わせたときはほとんど無力であり,C# など本物の静的型付け言語で同様のコードを書いたときの安心感には及びません。 少し Groovy に慣れてきた頃合いで C# 4.0 のケースと比較してください。
  15. static メソッドの本体では this として java.lang.Class インスタンスが得られる。
    groovy:000> class A {
    groovy:001>   static Object getThis() { return this; }
    groovy:002> }
    ===> true
    groovy:000> A.getThis()
    ===> class A
    groovy:000> A.getThis().getClass()
    ===> class java.lang.Class
    
    Groovy を Java の方言として使う限り,このことを意識する必要はほとんどありません。 ただし,static メソッドの中からじかに java.lang.Class とそのスーパークラス java.lang.Object のメソッドを使える (使ってもエラーにならない) ことに注意してください。

    このような static メソッドの解釈は,本来の Java 言語での static メソッドに対する synchronized 修飾と一致します。裏返して言えば Java はこの解釈を中途半端に実現しているわけです。 この意味で言えば Groovy は Java が本来そうあるはずだった姿,おおげさに言えば,内在する対称性の具体化になっています。 ある意味,null のインスタンス扱いにも同じことが言えます。
  16. ブロックを単体で使う場合は,適当なラベルを付ける必要がある。
    L1: { String s = 0xFF; System.out.println(s); }
    を実行すると,文字列型のローカル変数 s に,暗黙の型変換によって 16 進数 0xFF の Integer インスタンスの toString() の結果である "255" が代入され,255 と表示されます。 ブロックですから,実行後,ローカル変数 s はどこにも残りません。 ここでラベル L1: を省くと構文エラーになります。

    ラベルのないブロックを単体で使うことは日常的には滅多にありませんから, このチュートリアルを終えた後は,そんな制限もあったとおぼろげに覚えておくだけで十分です。 この制限はブロックをクロージャの省略記法 (5.1 節) と構文的に区別するために必要とされます。
  17. assert 文に,詳細メッセージを表す第2の式を付ける必要はほとんどない。 assert 文はつねに有効であり,無効にできない。
    Java では assert で失敗したとき,どんな値で失敗したのかを知る手がかりとして関係する変数値などを第2式に書いていました。 失敗時,第2式を評価した値の文字列表現が AssertionError インスタンス内蔵のメッセージとなります。
    assert a < 5: a;
    
    しかし,Groovy では assert で失敗したとき,その式の構成要素ごとの値が表示されますから,わざわざ第2式を付ける必要はほとんどありません (そうしたければ付けることもできます)。
    assert a < 5;
    
    Assertion failed: 
    
    assert a < 5
           | |
           | false
           10
    
    そのかわり Groovy では assert 文を無効にすることはできません。

4. もっとスクリプト言語らしく

Java を知っていれば,前章までの知識だけでさしあたり Groovy を使うことができる。 しかし,それでは Groovy を使う意味があまりない。

しかし,たとえ今ここで切り上げたとしても,Java で書かれたクラスの private な内部に再コンパイルなしに自由にアクセスできる便利なツールなどとして Groovy を使えますから,これまでの労力は決して無駄にはなりません。

Groovy にはコードをスクリプト言語らしく記述するための便宜がいろいろと用意されている。 2.1 節 では,1個以上の引数付きメソッド呼出しを最外の式として書くとき,引数を取り囲む丸括弧を省略できることを述べた。 ここでは,そのほかのスクリプト言語らしい特徴をいくつか説明する。 とりわけ前章で述べた JavaBeans プロパティの自動的な定義については 4.4 節 で再び取り上げる。

4.1 セミコロンと return の省略

行末のセミコロンは,あいまいさがなければ任意に省略してよい。 すでにいくつかの例でこれを見てきた。 閉じ波括弧「}」の直前のセミコロンも省略してよい。 対話シェルで例を示す。

groovy:000> for (int i = 0; i < 3; i++) { println i }
0
1
2
===> null

ちなみに,行末まで括弧が開いたままならば,自動的に次の行に継続する。

groovy:000> 1 + (
groovy:001> 2 + 1)
===> 4

文が連続したときは,最後の文の値が結果の値となる。

groovy:000> 1 + 1; 2 + 2
===> 4

ブロックの場合も,その最後の文の値が結果の値となる。

groovy:000> L1: { 1 + 1; 2 + 2 }
===> 4

if 文についても,選択されたブロックの最後の文の値が結果の値となる。

groovy:000> if (true) { println "yes"; "Y" } else { println "no"; "N" }
yes
===> Y
しかし,for 文については,ブロックの最後の文がそのまま for 文全体の結果の値とはなりません。 while 文についても同様です。 これらの文で最後に実行されて値を得ているのは普通は (暗黙裏に boolean 型にキャストされる) テスト式ですが,実際には,最後のテスト式の結果の値 (普通は false) ではなく,break 時も含めすべて null を戻り値とします。
groovy:000> for (int i = 0; i < 3; i++) { println i; i }
0
1
2
===> null
groovy:000> while ([]) { println "hi" }
===> null
groovy:000> while (true) { break }
===> null
ただし,このことはブロックで最後に実行された文の結果の値がブロック全体の結果の値になる,ということと矛盾しません。 for 文,while 文がもつブロックで最後に実行された文の値が確かにそのブロックの値になったとしても,それは for 文,while 文自体の値とはならずに捨てられ,for 文,while 文自体の値としては null が返される,というだけです。
groovy:000> int i; for (i = 0; i < 3; i++) { println i }; i
0
1
2
===> 3
このように for 文に続けて式文 (ここでは i) を置けば,for 文がその終了時に返す null も当然捨てられ,最後に実行される式文の値 (ここでは 3) が全体の結果の値となります。

ブロックで最後に実行された文の結果の値がブロック全体の結果の値になることからそれとなく予想されるように,メソッドの戻り値を指定するとき,必ずしも return 文を使う必要はない。 メソッド本体のブロックで最後に実行された文の値がそのまま,そのメソッド呼出しの結果の値,つまり戻り値になる。 戻り値を指定するためには,最後の文として単に,戻り値となる式 (厳密にいえば式文) を置けばよい。

それを利用した階乗関数の例を下記に示す。 ちなみに,ここでは桁あふれを起こさないように戻り値の型として BigInteger を指定している。 再帰の底では int の 1 が戻り値になるが,これは指定されている戻り値の型 BigInteger へと自動的にキャストされる。

groovy:000> BigInteger factorial(int n) {
groovy:001>   if (n == 1)
groovy:002>     1
groovy:003>   else
groovy:004>     n * factorial(n - 1)
groovy:005> }
===> true
groovy:000> factorial(100)
===> 933262154439441526816992388562667004907159682643816214685929638952175999932
29915608941463976156518286253697920827223758251185210916864000000000000000000000
000

なお,このようにクラスの外側でじかに関数を定義できることも Groovy のスクリプト言語らしい特徴の一つである。 ただし,これにはいくつかの注意点がある。 詳しくは 第6章で改めて見ることにしよう。 それまでは,さしあたり Ruby や Python の関数定義と同じに考えてよい。

4.2 型を指定しないときの def キーワード

変数を宣言したり,メソッドを定義するとき,今まですべて Java と同じように変数の型やメソッドの戻り値の型を明示してきた。しかし,型を明示しないで宣言したり定義したりすることもできる。 このとき,型の指定を省略して def を書く

groovy:000> def s = 0xFF; println s; println s.getClass()
255
class java.lang.Integer
===> null

この例で def だけで宣言されたローカル変数 s は, 型を決めていないから,代入されてくる値をそのまま自らの値とする。自動的な型変換は行われない。 整数 0xFF が代入されれば,その変数値の型はそのまま java.lang.Integer になる。

def で宣言した変数は実装上は java.lang.Object 型になりますが,陽に Object 型と宣言した変数とは観念的な意味で異なります。 def を使い,型を指定しないことは,型を意識しないコードであることの表明です。 型決めは処理系に任せた!! というわけです。 潜在的には,コンパイル時の静的な型推論の対象になります。 ただし,今のところ,抽象メソッドを実装するメソッドを Groovy で書くときはその戻り値型や引数型を明示した方が安全です。

より正確に言えば,Groovy の def は何かに名前を与えて定義 (define) するためのキーワードである。 型の指定は省略することも記述することもできる。 型を省略したときは前述のとおりである。 その一方,型を記述するときは def を省いてよい。このときは,慣れ親しんだ Java の宣言と同じ構文になる。

普通は誰もそうしませんが,def と型の両方を記述することもできます。 またクラスの定義や仮引数の宣言に def を付けることができます。 下記はその極端な例です。
groovy:000> def class A {
groovy:001>   def String s = "abc"
groovy:002>   def String add(def String t) { s + t }
groovy:003> }
===> true
groovy:000> a = new A()
===> A@1a9bea3
groovy:000> a.s
===> abc
groovy:000> a.add("def")
===> abcdef

構文から宣言であることが明らかならば,def と型の両方を省略してよい。 メソッド (関数) の仮引数の宣言と catch 節の例外パラメタがこれに該当する。

groovy:000> def foo(def n) { n + 10 }
===> true
groovy:000> foo(1)
===> 11
groovy:000> def foo(n) { n + 100 }
===> true
groovy:000> foo(1)
===> 101
groovy:000> try { 5 / 0 } catch (e) { println e }
java.lang.ArithmeticException: Division by zero
===> null

しばしば Groovy スクリプトとして紹介されるところの,メソッドやフィールドを def で定義し,仮引数並びに変数名だけを連ねているプログラムは,実はこのような省略の産物である。

Python ファンと Ruby ファンへの警告: Groovy の def は関数やメソッドを定義するだけでなく,変数を宣言するためにも使います。 とりわけローカル変数を宣言するためにも使うことに注意してください。 ローカル変数には宣言が必要です。
def foo(n) {
  def x = n + 100
  return x
}
悪いことに,もしも宣言を忘れると Python, Ruby はもちろん Java とも異なる振舞を示します。 詳しくは 6.4 節で説明します。

ちなみに前節の factorial 関数を型を明示せずに def を使って書くと,少し大きな数に対して Integer が桁あふれを起こす。 これは演算がすべて int ないし Integer どうしに限られるためである。

groovy:000> def factorial(n) {
groovy:001>   if (n == 1)
groovy:002>     1
groovy:003>   else
groovy:004>     n * factorial(n - 1)
groovy:005> }
===> true
groovy:000> factorial(100)
===> 0
groovy:000> factorial(5)
===> 120
固定ビット幅の整数演算で桁あふれが生じたとき,スクリプト言語らしからぬことに,黙って誤った結果を返します。 良く言えば,桁あふれに対して Java と同じ振舞を示します。

桁あふれを防ぐには,再帰の底の戻り値として 1 のかわりに BigInteger にキャストした (BigInteger) 1 あるいは 1 as BigInteger を置くか,または Groovy の BigInteger 定数の記法を使った 1G を置けばよい。 もしくは単に引数として BigInteger 値を与えればよい。

groovy:000> factorial(100G)
===> 933262154439441526816992388562667004907159682643816214685929638952175999932
29915608941463976156518286253697920827223758251185210916864000000000000000000000
000
厳密に言えば,引数として BigInteger 値を与える方法は,再帰の底のときだけ戻り値の型が Integer になるという点で他と振舞が違います。
groovy:000> factorial(1G).getClass()
===> class java.lang.Integer

4.3 Groovy JDK

今まで println メソッドを説明せずに使ってきた。 これが何者なのか,ここで明らかにしよう。

インスタンスのクラスに対して定義されていないメソッドが呼び出されたとき,Groovy はただちには誤りとしない。 もしもあらかじめ別に用意した特定のメソッド群の中に適合するものがあれば,インスタンスと元々の引数を引き渡してそれを呼び出す。 println メソッドの実体は,このようなメソッドの一つであり,その具体的な実装は Groovy のソースの groovy-2.0.1/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java の下記の部分にある。

    /**
     * Print a value formatted Groovy style (followed by a newline) to self
     * if it is a Writer, otherwise to the standard output stream.
     *
     * @param self  any Object
     * @param value the value to print
     * @since 1.0
     */
    public static void println(Object self, Object value) {
        // we won't get here if we are a PrintWriter
        if (self instanceof Writer) {
            final PrintWriter pw = new GroovyPrintWriter((Writer) self);
            pw.println(value);
        } else {
            System.out.println(InvokerHelper.toString(value));
        }
    }

ある変数 x を引数として println(x) を呼び出したとしよう。

このとき,その文脈での this をレシーバとして println という名前のメソッドが探索される。 this が属するクラスからメソッドの探索が始まる。 しかし,すべてのクラスの共通基底クラス java.lang.Object まで探索しても適合するものがないとしよう。 このとき DefaultGroovyMethods クラスの public static void println(Object self, Object value) が選択される。 その呼出し時,第1仮引数 self には this が,第2仮引数 value には x がそれぞれ引き渡される。

つまり,Groovy の中では,事実上 Java.lang.Object に1引数のメソッド public void Object#println(Object value) が追加されていると見ることができる。 第3章 で述べた boolean is(Object) メソッドも,このような追加されたメソッドである。 ほかにも多数のメソッドが追加されている。 JDK に対して追加されたメソッド (methods added to the JDK) からなる Groovy の (中にだけで通用する) ライブラリを Groovy JDK と呼ぶ。

C# ファンへ: Groovy JDK とは,Groovy が標準で用意した拡張メソッド (extension method) のライブラリであると考えてください。

void を戻り値の型とする Java メソッドを呼び出したとき,本来ならば戻り値はないが,Groovy は null をその便宜的な結果の値として返す。 2.1 節以来,今まで君は println の結果の値として null を見てきた。 それは上記に引用したメソッド定義の戻り値の型 void に由来している。

Groovy JDK の基礎的な資料としては Groovy JDK API Specification [codehaus.org] があります。 第8章 に挙げた参考文献も参照してください。

4.3.1 this と組込み関数

この println のメソッド定義から分かるように,レシーバにあたる仮引数 self は,それが java.io.Writer インスタンスである場合を除き,メソッドの中で無視される。 例えば,整数 1 は Groovy の中では java.lang.Integer ひいては java.lang.Object のインスタンスだから,もしそうしたければ 1.println(expression) のように呼び出すことができる。 しかし,たとえそうしてもレシーバとしての 1 は無視され,意味をもたない。

groovy:000> 1.println(3)
3
===> null

println メソッドの呼出しでわざわざ (java.io.Writer インスタンス以外の) レシーバを書くことは無駄であるだけでなく,実際にはそうでないのにあたかもレシーバの値に何か意味があるように見えて紛らわしい。 そのため普通はレシーバを陽に書かない。このとき Java と同じく this が暗黙裏にレシーバとして使われる。

this の値そのものは呼び出す文脈により異なるが,(java.io.Writer 派生クラスのメソッド定義の中などを除き) どこで呼び出そうとも,どのみちそのレシーバは無視されるから,引数を印字して改行するという println の振舞は変わらない。 クラス定義の内側ではない場所,つまりトップレベル (6.1 節) での println の呼出しも例外ではない。 暗黙裏に作られる groovy.lang.Script の派生クラスのインスタンスがトップレベルの this として与えられ,println が実行される。

つまり,事実上,どこからでも呼び出せる組込み関数として println を使うことができる。

より一般に,java.lang.Object のインスタンスをレシーバとし,実際の処理ではレシーバを無視するメソッドは,どこからでも呼び出せる組込み関数として使うことができる。

Ruby ファンへ: 全く同じような議論を Ruby の Kernel モジュールで定義される「組込み関数」に関して聞いたことがあるはずです。 もし初耳ならば,Ruby チュートリアル - 1. てほどき - 1.4 self と private メソッド などを読んでみるとよいでしょう。 Ruby の真実の一端がそこにあります。 何気ない地味な部分ですが,これも Groovy が Ruby からインスピレーションを受けた点の一つと言ってよいでしょう。

4.4 プロパティとフィールドと .@ 演算子

Groovy は Java Beans プロパティを構文的にサポートしている。 クラスにゲッター・メソッド getPoi やセッター・メソッド setPoi が定義されているとき, これをフィールド poi のようにアクセスできる。 例を示す。

groovy:000> class A {
groovy:001>    private int i
groovy:002>    public int getPoi() { i }
groovy:003>    public void setPoi(int x) { i = x; printf "poi is %d%n", i }
groovy:004> }
===> true
groovy:000> a = new A ()
===> A@e5f21e
groovy:000> a.poi = 123
poi is 123
===> 123
groovy:000> a.poi
===> 123
この例に見るように,a.poi = 123a.setPoi(123) をした後,右辺の値を結果の値として返します。 つまり,L: { a.setPoi(123); 123 } というブロックと実質的に等価です。 一見すると無駄に思えますが, x = a.poi = result のような代入を可能にするために必要です。

さらなる便宜として,第3章で述べたように,フィールドにアクセス制御を指定しないとき, そのフィールドは自動的に Java Beans プロパティとして定義される。 例えば,

class A {
    int poi
}

と書いたとき,実際には下記のような Groovy コードを書いたことと等価である。

class A {
    private int poi;
    public int getPoi() { poi }
    public void setPoi(int value) { poi = value }
}
2.4 節 のように groovyc でコンパイルし,javap -private A または javap -private -verbose A の出力をみれば,この自動定義の結果を確かめられます。

ここでさらにゲッター・メソッドまたはセッター・メソッドを陽に定義すれば,デフォルトの振舞を変更できる。 下記は,与えられた値の 10 倍の値をプロパティ値としてセットするように定義した (わざとらしい) 例である。

groovy:000> class A {
groovy:001>   int poi
groovy:002>   public void setPoi(int x) { poi = x * 10 }
groovy:003> }
===> true
groovy:000> a = new A()
===> A@52ded2
groovy:000> a.poi = 1
===> 1
groovy:000> a.poi
===> 10
groovy:000> a.@poi = 2
===> 2
groovy:000> a.poi
===> 2

ここで a.@poi = 2.@ は,メソッドをバイパスして,フィールドに直接アクセスするための演算子である。

この演算子はもしかしたら Ruby の インスタンス変数 の記法の影響を受けているかもしれません。

4.5 リスト,マップ,レンジ

Groovy はリストとして,第3章 で学んだように式の並びをカギ括弧で囲んで java.util.ArrayList インスタンスを構築できる。 この構築方法は Ruby の Array や Python のリストに似ている。

groovy:000> x = [3, 1, 4, 1, 5, 9]
===> [3, 1, 4, 1, 5, 9]
groovy:000> x.getClass()
===> class java.util.ArrayList

リストから生の配列へキャスト演算または as 演算だけで変換できる。

groovy:000> y = (int[]) x
===> [I@e2c656
groovy:000> y.getClass()
===> class [I
groovy:000> z = x as int[]
===> [I@1d1cdf7
groovy:000> z.getClass()
===> class [I

変換前後の値は等しい。 オブジェクトとしては新しく作られたものだから,アイデンティティは異なる。

groovy:000> print x
[3, 1, 4, 1, 5, 9]===> null
groovy:000> print y
[3, 1, 4, 1, 5, 9]===> null
groovy:000> x == y
===> true
groovy:000> x.is y
===> false

リストと生の配列のどちらも Ruby の Array が備えているようなメソッドを使うことができる (5.5 節 参照)。

groovy:000> x.collect { it + 100 }
===> [103, 101, 104, 101, 105, 109]
groovy:000> y.collect { it + 100 }
===> [103, 101, 104, 101, 105, 109]
Ruby では次のように書くことができます。
x = [3, 1, 4, 1, 5, 9]
x.collect {|e| e + 100 }
よく似ていますが,Groovy では e を宣言せずに暗黙の it を使っています。 e を宣言する場合,Groovy ではこうします (5.1 節 参照)。
x.collect { e -> e + 100 }

Ruby の Array に適用できるような演算子も使える。下記では 2 を末尾に追加している。

groovy:000> x << 2
===> [3, 1, 4, 1, 5, 9, 2]
groovy:000> x
===> [3, 1, 4, 1, 5, 9, 2]

実際には x << 2x.leftShift(2) の構文上の糖衣である。

ただし,生の配列は Java と同じくあくまで固定長だから,末尾に要素を追加することはできない。 エラーメッセージから,leftShift という生のメソッド名を隠している糖衣のほころびを見ることができる。

groovy:000> y << 2
ERROR groovy.lang.MissingMethodException:
No signature of method: [I.leftShift() is applicable for argument types: (java.l
ang.Integer) values: [2]
        at groovysh_evaluate.run (groovysh_evaluate:2)
        ...

Groovy のマップの構文は Ruby の Hash とも Python の辞書とも異なる。 Groovy では,カギ括弧で囲みカンマで区切った並びの各要素として,コロンで組みにしたキーと値のペアを与える。 こうして構築される java.util.Map インスタンスは正確にはその実装クラス java.util.LinkedHashMap のインスタンスである。

groovy:000> apples = ["hu": "alma", "fi": "omena", "tr": "elma"]
===> {hu=alma, fi=omena, tr=elma}
groovy:000> apples.getClass()
===> class java.util.LinkedHashMap

ここでマップを構築したときの結果の値を見ると,並びがカギ括弧ではなく波括弧 { } で囲まれていること,キーと値が等号で組みにされていることに気付く。 これは Java による実装クラス java.util.LinkedHashMaptoString() の戻り値がそのまま表示されているからである。 Groovy 自身のマップの構文ではないことに注意しよう。

またこのとき,マップの各要素が与えたとおりの順序で表示されているのは決して偶然ではない。 java.util.LinkedHashMap はマップへのキーの追加順序を保存する。

ちなみにこのマップは,言語コードからその言語で 林檎 にあたる単語への写像 (mapping) です。

キーに対する値をセットしたり取得するときは,添字演算子を使うことができる。

groovy:000> apples["eo"] = "pomo"
===> pomo
groovy:000> println apples
[hu:alma, fi:omena, tr:elma, eo:pomo]
===> null

Groovy が Ruby に直接ヒントを得たデータ構造としてレンジ (range, 範囲) がある。 与えられた範囲の値を順に格納するように構成したリストと似ているが,始端と終端以外の値をじかに持たずに要求に応じて作って返すだけだから, リストに比べて空間的な効率が極めて良い。

終端を含むときの構築方法は Ruby の Range と同じである。

groovy:000> ii = 1..5
===> 1..5
groovy:000> ii.getClass()
===> class groovy.lang.IntRange
groovy:000> for (i in ii) printf("%d, ", i)
1, 2, 3, 4, 5, ===> null

Ruby と異なり,終端を含まないときは 構築演算子として ..< を使う。

groovy:000> jj = 1 ..< 5
===> 1..4

整数に限らず,next()previous() のメソッドを実装し, java.lang.Comparable インタフェースを実装しているならば,何であれレンジの始端と終端にできる。

groovy:000> "s".previous()
===> r
groovy:000> "s".next()
===> t
groovy:000> "s" instanceof Comparable
===> true
groovy:000> cc = "a".."z"
===> a..z

始端と終端は fromto で得られる。 これは Comparable getFrom()Comparable getTo() で実装されるプロパティである。

groovy:000> cc.from
===> a
groovy:000> cc.to
===> z

レンジは java.util.Collection であり,さらに言えば java.util.List である。

groovy:000> cc instanceof java.util.Collection
===> true
groovy:000> cc instanceof java.util.List
===> true
groovy:000> cc.getClass()
===> class groovy.lang.ObjectRange

したがって,Groovy の他の Collection インスタンスと同じく様々な操作が可能である (5.5 節 参照)。 そうしようと思えば,最小値と最大値を得るための (レンジにとっては愚かな) 手段として min()max() を使うこともできる。

groovy:000> cc.min()
===> a
groovy:000> cc.max()
===> z
groovy:000> cc.each { printf("%s-", it) }; println() 
a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-
===> null
最後の例は Ruby では次のように書くことができます。
cc = "a".."z"
cc.each {|e| printf("%s-", e)}; puts

この節では二,三の例で,等価な Ruby コードを示すこと以外,特に説明せずにクロージャを使ってきた。 実行結果からそのおおよその動作と意味はつかめていると思う。 最後の例では eachfor 文と同じ種類の制御文に見えているかもしれない。 事実,それは制御変数 it にレンジの各要素を代入して { } 内にある printf メソッドの呼出しを繰り返している。

しかし,これは言語に作り付けの制御文ではなく,自由に定義して組立てることのできる単なるメソッドの呼出し式である。 ただ,そのメソッドが,コードのかたまりである「クロージャ」を引数にとることで,制御文と同じような振舞を実現しているにすぎない。 逆にいえば,クロージャを使えば,君独自の新しい制御文 (であるように見え,そのように機能するもの) をメソッドとして作ることができる。

クロージャを引数にとるメソッドは,この数十年間,関数型言語の世界で高階関数 (higher-order function) として広く知られ活用されてきたものと基本的に同じである。 J. Hughes が 「なぜ関数プログラミングは重要か」 [sampou.org] でいみじくも説明しているように,高階関数は,問題を部分に分解し組立てるための有用な道具になる。 それなしでは考えもしなかったかたちでコードを再利用でき,生産性向上に役立つライブラリを構築できる。 その効用は Miranda や Haskell のような厳密な意味での関数型言語に限らず, Lisp や ML のような純粋ではない関数型言語,さらには Smalltalk-80 や Ruby にも及び,現在さらに幅広く普及しようとしている。

次章では,いよいよ,このクロージャについて学ぼう。

もしもクロージャのことを何かオブジェクト指向の世界にとって新参の余所者のように思っているならば,歴史を訪ね直すべきです。

オブジェクト指向言語の主要な父祖の一つである Smalltalk は 1980 年当時すでにブロック (block) という用語でクロージャに相当する言語要素を導入し,(それ自身はそうは呼称していなかったものの) 高階関数プログラミングをその基盤としていました。 条件分岐の if 文でさえ,Boolean クラスに対するブロック付きメソッドとして与えられていました。
(self respondsTo: operator)
  ifFalse: [self error: 'operation not understood']
その後の C++ の隆盛により,いったんはほとんど忘れ去られましたが, もともとその最初の日々からオブジェクト指向はクロージャとともにありました。 その伝統は部分的に Ruby に継承され,さらに Groovy が Ruby から継いでいます。 クロージャを活用することは,ある意味,オブジェクト指向言語が長く忘れていた力を復活させることです。 クロージャは決して新参の余所者ではなく,たまたま一時疎遠になっていた旧友,幼馴染みというべき存在です。

参考: A. Goldberg & D. Robson: "Smalltalk-80: The Language and its Implementation", Addison-Wesley, 1983, ISBN 0-201-11371-6

5. クロージャ

クロージャ (closure, 関数閉包) とは,一般に,環境 (つまり,自由変数からその変数値への写像) を伴って引き渡される関数である。

特に Groovy では groovy.lang.Closure クラスのインスタンスを意味する。 その実態は一般的なクロージャの定義と矛盾しておらず,妥当な命名である。 Groovy は Closure のインスタンスを構築する特別な構文を下記のように用意している。

{ 仮引数並び -> 宣言や文の並び }

ここで「仮引数並び」はメソッド定義での仮引数並びと同様である。 「宣言や文の並び」はメソッド定義でのメソッド本体と同様である。

Closure インスタンスに実引数を与えて,メソッドのように呼び出すことができる。 メソッド呼出しと同じく,この呼出しが最外の式であって,実引数が1個以上あれば,実引数を取り囲む丸括弧を省略してよい。

groovy:000> c = { int x, int y -> int z = x + y; return z * 2 }
===> groovysh_evaluate$_run_closure1@a2bf0f
groovy:000> c(2, 3)
===> 10
groovy:000> c 2, 3
===> 10
groovy:000> c instanceof Closure
===> true
groovy:000> c.class.superclass
===> class groovy.lang.Closure

この例から分かるように,各クロージャの Java VM 上の実装クラスは, groovy.lang.Closure の派生クラスとして Groovy によって自動的に定義される。 これは実装詳細であり,普段は Closure インスタンスとしてだけ意識すればよい。

5.1 クロージャの省略記法

1引数のクロージャで仮引数の型を指定しないならば,仮引数の並びと -> を省略してよい。 仮引数は it として参照される。

groovy:000> c = { it * 2 }
===> groovysh_evaluate$_run_closure1@b1164d
groovy:000> c 3
===> 6

0引数のクロージャならば,-> を省略してよい。

groovy:000> c = { println "Hi ho!" } 
===> groovysh_evaluate$_run_closure1@500082
groovy:000> c()
Hi ho!
===> null

このように省略したとき,クロージャとブロックを,それ自体の構文から区別することはできない。 ただし,ほとんどの場合,これは問題にならない。 式の一部として出現した場合は,クロージャと判定されるからだ。 この例では代入の右辺として出現しているから,クロージャと判定される。

しかし,文として出現した場合は,どちらなのか判定できないため,構文解析のエラーが発生する。 その場合,-> を省略しないで書くことによって,クロージャであると明示できる (一方,第3章 では,ブロックであると明示するために適当なラベルを接頭した)。

groovy:000> def say() { { -> "Nukata" } } 
===> true
groovy:000> say()
===> groovysh_evaluate$_say_closure1@62974e
groovy:000> say()()
===> Nukata

5.2 静的スコープと this

クロージャとは単なる引数付きのコードの並びではない。 もしそうだったならば,クロージャを引数にとるメソッドを (普通の制御文であるかのように) なにげなく自然に使うことができなくなる。 そうしないための鍵は自由変数の取り扱いにある。

クロージャの自由変数 (free variable) とは, クロージャの仮引数でも,クロージャ内部で宣言された変数でもない,クロージャの外部からの変数である。

例えば
{ x, y -> int z = a * (x + y); return z }
では,a はこのクロージャの自由変数, xy は仮引数 (束縛変数, bound variable), z はクロージャ内部で宣言されたローカル変数 (束縛変数) です。

クロージャは自由変数を,クロージャが作られた場所から見える変数と解釈して参照する。 たとえクロージャがメソッドの戻り値として返され,あるいは引数として渡され, どこか見知らぬ別のクラスのメソッドの中に持っていかれたとしても,クロージャが呼び出されたとき, それが参照する自由変数は,必ず,クロージャの生まれ故郷の風景にある変数である。 クロージャは,遠くにあってその風景を見るよすがとして,常に環境 (environment) を随伴させている。

オブジェクト指向言語では,この議論はとりわけクロージャ内の this が何を指すかという問題と関係する。 結論は明快である。 this をクロージャにとって自由変数であるとしたとき, クロージャ内の this は,クロージャがどこで実行されようとも,クロージャが作られた産土の this を参照する。

下記の例は,クラス A のメソッドの中で作られたクロージャが,どこへ持っていかれ,どこで実行されようとも, あくまでクラス A のインスタンスを this として参照することを示している。

groovy:000> class A {
groovy:001>   def c() { return {-> println this} }
groovy:002> }
===> true
groovy:000> class B {
groovy:001>   def f(c) { println this; c() }
groovy:002> }
===> true
groovy:000> a = new A()
===> A@969e65
groovy:000> c = a.c()
===> A$_c_closure1@8b30ae
groovy:000> c()
A@969e65
===> null
groovy:000> b = new B()
===> B@9a2097
groovy:000> b.f(c)
B@9a2097
A@969e65
===> null
逆に考えてみましょう。もしもクロージャの中の this が,持っていかれた先々の this を意味するとしたらどうなるでしょうか。 this に対して定義されているはずのメソッドが,そこでは使えないかもしれませんし, 別の意味で定義されているかもしれません。

これは 1970 年に提起された FUNARG 問題 [wikipedia.org] を今の言葉で表現したものです。 関数を変数に代入したり,戻り値としたりするプログラミング言語は,歴史的には Lisp が最初期のものでしたが, 長い間このような,今の目で見れば気違いじみたことが平然と行われていました。時代にさきがけて 1960 年に Algol 60 [masswerk.at] が静的スコープを採った意義は当時まだ広く理解されていませんでした。

静的スコープ (static scope, 字句的スコープ lexical scope とも) とは,変数のスコープつまり有効範囲の種類の一つであり,有効範囲がプログラムの静的な字面から決まるものをいう。 クロージャは,その郷土愛にあふれる振舞によって静的スコープを具現化している。 静的スコープでは,プログラムに書かれた変数名が,どの変数のことを意味するのか,プログラムの実行前に分かる。 この長所によって,Groovy に限らず,現代のプログラミング言語のほとんどが静的スコープを採用している。

もちろん,Java も静的スコープを採っています。 anonymous クラスからローカル変数を自由変数として参照するために final 指定を必要とするという付加的な制約はありますが,静的スコープとは矛盾しません。

当初,静的スコープを採用しなかった言語も,その多くは進化の途上でそれを採用しています。 近代的な Lisp の歴史は,Algol 60 にならって静的スコープを全面的に採用した Scheme,および概ね同じ設計者により旧来の Lisp との折衷で作られた Common Lisp から始まります (標準 Pascal によるモダンな Lisp の小さな実装 §4 および PLY で作る Algol 60 処理系 §2 も見てください)。

今日,ラムダ式とクロージャはほとんど同義語のように扱われています。 しかし,Algol の血が混じる前の Lisp では両者は別物でした。当時,
'(lambda (x) (f x a))
を評価した結果はラムダ式であってもクロージャではありませんでした。 自由変数 a はラムダ式が評価された場所から見える a と解釈されました。 クロージャにするには,
(function (lambda (x) (f x a)))
のようにする必要がありましたが,こうすることは効率と引き換えでした。 純血種の Lisp の末裔である Emacs Lisp はさらに極端な立場をとり,従来たとえこうしてもクロージャを作らず,単にバイトコンパイルの対象として印づけるだけでした。 ただし,今は静的スコープを採用する方向へゆっくり移行しており, 従来どおりの振舞に加えて,オプションとしてクロージャを作らせることもできます。 Emacs Lisp 参照マニュアルの 11.9.4 Using Lexical Binding [gnu.org] と 12.7 Anonymous Functions [gnu.org] を参照してください。

Python は当初は独特なスコープ則を採っていましたが,Python 2.1 で静的スコープを採用しました (PEP 227 [python.org])。 これはプログラムの意味論の根幹にかかわる非互換的・抜本的な改良でした。 これ以前の Python の文献は独特なスコープ則の説明に,初心者はその理解に苦労していました。 それが突然すべて解決したのです。 これと比べると,互換性を犠牲にした大改訂版のように言われている Python 3.0 は確かにちくちくと非互換性がありますが, 実際には,いつもの改訂だったらとても理由を正当化できない どちらでも大差ないような変更が大半です。 本稿著者による公開プログラムでは,例えば Prelude.py の outchar 関数に Python の静的スコープの利用例があります。
P. Graham が 2002 年のエッセイ "Revenge of the Nerds" [paulgraham.com] (和訳 [practical-scheme.net]) で,クロージャの強力さを説明するために accumulator generator を例にとっていくつかの言語でコードを書いています。その一部を示します。
Scheme:
(define (foo n) (lambda (i) (set! n (+ n i)) n))
Arc:
(def foo (n) [++ n _])
これらと同じように Groovy もクロージャを使うことができます。 波括弧が二重になっていますが,外側の波括弧は関数本体をくくる括弧です。 内側の波括弧は関数の戻り値としてのクロージャを構成する括弧です。
Groovy:
def foo(n) {{i -> n += i}}
groovy:000> def foo(n) {{i -> n += i}}
===> true
groovy:000> f = foo(3) 
===> groovysh_evaluate$_foo_closure1@f91dd6
groovy:000> f(1)
===> 4
groovy:000> f(5)
===> 9
groovy:000> g = foo(100)
===> groovysh_evaluate$_foo_closure1@de5d00
groovy:000> g(2)
===> 102
groovy:000> g(3)
===> 105
groovy:000> f(2)
===> 11
Python ファンへ: 同エッセイでは Python が否定的に書かれています。 滑った感がある Python 3.* ですが,実のあるささやかな新機能の一つとして PEP 227 の静的スコープを補完する PEP 3104 [python.org] の nonlocal 宣言があります。 これを利用すれば同エッセイで批判されている Python プログラムを改良できます。 どう書き直せばよいか?,いまだ残る課題は何か? は Python ファンの読者への課題とします。

5.2.1 補足: delegate プロパティ

前節ではクロージャのスコープについての基本的な原則を述べたが,実はまだこれは真実のすべてではない。 前節の議論と矛盾しない範囲で Groovy は,その出生地からは見えないメソッドをクロージャから利用できるように, delegate プロパティを使ってスコープを拡張している。

delegate プロパティは Groovy のどの Closure インスタンスにもある。 このプロパティはクロージャが作られた時点では,その this の値と同じに初期化されるが,後から自由に変更できる。 そして,もしもクロージャ内で変数やメソッドの名前を解決できないときは,this のかわりに delegate の値を起点として名前の探索が再試行される。

下記のコードでこのことを説明しよう。

class A { 
  // static foo(x) { x - 10 }
  static Closure c = { foo(bar) }
}

println A.c.delegate            // => class A

class B { 
  def foo(x) { x + 1 }
  def bar = 100
  def c = A.c
}

b = new B ()
b.c.delegate = b
println b.c()                   // => 101

まず,クラス A に,静的フィールドとしてクロージャ c を設ける。 このクロージャの delegate プロパティの初期値は,その this と同じく class A である。 クロージャ内では bar を引数として foo を呼び出すが,foobar のどちらもクロージャが作られたクラス A からは見えないことに注意しよう。

次に,クラス B にフィールド c を設ける。初期値は今しがたのクロージャ A.cとする。

それから,クラス B のインスタンス b を作り,b.c.delegate = b として delegate プロパティの値を b とする。

最後に b.c() を呼び出すと,クロージャ内の foo(bar) が実行される。 しかし,foo という名前はそのクロージャの出生地であるクラス A からは見つからない。そこで delegate プロパティの値であるインスタンス b を起点として探索し直すと,クラス B のメソッド foo が見つかる。 bar についても同様に探索し直してクラス Bbar が見つかる。 こうして 100 + 1 が実行されて 101 が得られる。

class A
101

もしもこのとき,クラス A で今はコメントアウトされている foo メソッドの定義を活かしたとすると,当然そちらが優先して使われることになる。 したがって,この場合は 100 - 10 が実行されて 90 が得られる。

つまり,静的スコープの基本原則に従い,可能な限りそのクロージャの出生地 (で決まる this) が優先される。 それで解決されない名前についてだけ,delegate による動的スコープが実施される。

この例のように,delegate プロパティは主に,よそのクロージャを取り入れて自分のメソッドのように使うためにあります。 ある意味,かたくななクロージャに,郷に入っては郷に従うダイナミックさを与えるわけです。

5.3 クロージャ実引数の特別規則

Ruby ファンへ: ここから本章の終わりまでは Groovy が Ruby にインスパイアされた最大の部分にあたります。 そっくりそのままのところも,そうではないところもありますが,なじみ深い Ruby のおもかげを,あちこちに見ることができます。

前節の例で示したように,クロージャは,普通の式と変わるところなく実引数としてメソッドに渡すことができる。 機能としてはこれで十分だが,さらに Groovy は,人間が利用しやすいように,ある特別な構文規則を導入している。 メソッド呼出しの丸括弧のに波括弧をおいてクロージャを書くことができる,という規則である。

下記に例を示す。

groovy:000> def f(x, y, z) { println x; println y; println z }
===> true
groovy:000> f(1, 2, 3)
1
2
3
===> null
groovy:000> f(1, 2, {a -> a + 2})
1
2
groovysh_evaluate$_run_closure1@dd95f6
===> null
groovy:000> f(1, 2) { a -> a + 2 }
1
2
groovysh_evaluate$_run_closure1@87e18f
===> null

最後の呼出し f(1, 2) { a -> a + 2 } に注目しよう。 これは意味としては f(1, 2, {a -> a + 2}) と変わらない。 しかし,人の目には,あたかも while (…) { … }synchronized (…) { … } と同類の制御構造に見える。 つまり,クロージャを最後の引数とするメソッドを定義することで,見かけ上,新しい制御構造を定義できる。

自由変数を,クロージャが書かれた場所から見える変数として参照する,という静的スコープのルールは, このようなクロージャの使い方とマッチしている。

0引数または1引数のクロージャのとき,省略記法を使えば,ぎこちない「->」を消すことができる。

groovy:000> f (1, 2) { it + 2 }
ただし,この特別規則は可能な唯一の選択肢ではありません。 理論的な見地からはカリー化 (currying) を使うのが自然だったはずです。
def my_each(list) {
  return { closure ->
    list.each(closure)
  }
}

my_each([2, 7, 1, 8]) ({ println it + 100 })
今の Groovy でカリー化を使った場合,現行の特別規則を避けるために,このように波括弧を丸括弧で囲む必要があります。 最外の式として,この余分のメソッド呼出しの丸括弧は,本来は省略できたはずでした。 しかし,カリー化は,メソッド適用の最適化を伴わなければ効率的には不利になります。 現行の Groovy が (そしてこのやり方の元になった Ruby も) 一概に悪いとは言えません。

5.4 Ruby のブロックとの違い

クロージャを末尾の引数にとる Groovy のメソッドは Ruby の ブロック付きメソッド と多くの共通点をもつ。 しかし,Groovy は決して Ruby の単純な引き写しではない。

紛らわしいのですが,Ruby は,クロージャに相当する言語要素を Smalltalk にならってブロックと呼びます。 Groovy は,ブロックという単語を Algol から BCPL, C, Java を経て伝えられてきた用語と同じ意味で使います。

Ruby のブロック付きメソッドには,暗黙のうちに渡される (Ruby の用語法でいう) ブロックと,それを呼び出す yield,優先順位の異なる2種類の文括弧など,歴史的な事情を引きずる数々の言語要素が関係している。 一方,Groovy は,そのほとんどを捨て去り,一般概念に沿ったクロージャを導入し,使い勝手のために 少数の簡単な省略記法や特別規則を採用することで同等の効果を達成している。

Groovy のクロージャは,オブジェクトとして,その参照を自由に受け渡すことができるとともに, メソッドと同じく,丸括弧で引数を与えて呼び出すこと (さらにはメソッド呼出しと同じ規則で丸括弧を省略すること) ができます。 つまり,ファーストクラスのオブジェクトとしての関数そのものです。 計算機科学の一般概念としての「クロージャ」が,delegate プロパティの追加機構を除き,そのまま Groovy のクロージャにあてはまります。

言い換えれば,ブロックがファーストクラスのオブジェクトだった Smalltalk の単純さへ戻っている,ともいえます。 しかし,Smalltalk は if-then-else の構造さえもブロック付きメソッドで用意する,今となってはなじみのない言語です。 よく知られている Java と Ruby から自然な類推ですぐに使える外形になるように留めたのが,今の Groovy だと言ってよいかもしれません。

Groovy が一般概念に沿ったクロージャを採用したことの帰結として,Ruby との間に特筆すべき重要な違いが一つある。

Ruby のブロックの中で return を実行したときは,ブロックだけでなくそれを取り囲むメソッドが終了する。 しかし,Groovy のクロージャの中で return を実行したときは,Ruby の next でブロックが終了するのと同じように,そのクロージャの呼出しだけが終了する。 つまり,おおまかに言って,次の等式が成り立つ。

Groovy の return = Ruby の next

例を示そう。

Groovy は Ruby にならって数に対し times メソッドを追加している。 これは,その数だけの回数,クロージャを呼び出すメソッドである。 このクロージャの中で return したとき,次の繰り返しへと進む。 これは Ruby の next と正確に同じ動作である。 ループを強制終了するには,例外を throw して times メソッドから脱出する必要がある。

groovy:000> 5.times { print "No."; println it }
No.0
No.1
No.2
No.3
No.4
===> null
groovy:000> 5.times { print "No."; if (it == 2) return; println it }
No.0
No.1
No.No.3
No.4
===> null

しかし,純粋にクロージャとして考えれば,return で終了する Groovy の振舞の方が素直で理解しやすい。 Ruby のブロックを途中で終了するにはどうすればよいかは,多くの Ruby 初心者がつまずく問題である。

このように Groovy は言語設計の基礎部分では Ruby の複雑さを大胆に省き,計算機科学の常識のもとで簡明に再構成しています。 しかし,その結果は,結局,Ruby に似たものになっています。次の節ではそれを見てみます。

5.5 Ruby 由来のイテレータ

前節の times メソッドのほか,Groovy は Ruby に由来するイテレータをいくつか追加している。

groovy:000> 1.upto(3) { printf "%d ", it }
1 2 3 ===> null
groovy:000> 3.downto(1) { printf "%d ", it }    
3 2 1 ===> null
groovy:000> 1.step(10, 2) { printf "%d ", it }
1 3 5 7 9 ===> null
groovy:000> [3, 1, 9].each { printf "%d ", it }
3 1 9 ===> [3, 1, 9]

とりわけ,Ruby の Enumerable モジュールのメソッドと同様のメソッドをいくつか Groovy JDK として用意している。

groovy:000> x = [3, 1, 4, 1, 5, 9]
===> [3, 1, 4, 1, 5, 9]
groovy:000> x.collect { it + 0.5 }
===> [3.5, 1.5, 4.5, 1.5, 5.5, 9.5]
groovy:000> x.findAll { it > 2 }
===> [3, 4, 5, 9]
groovy:000> x.inject(1) { x, y -> x * y }
===> 540
groovy:000> x.sort { x, y -> y <=> x }
===> [9, 5, 4, 3, 1, 1]
groovy:000> x.min()
===> 1
groovy:000> x.max()
===> 9

この部分だけ見るならば,Groovy は Ruby の1方言であるとしか思えない。

ここで
x.findAll { it > 2 }
については少し説明が必要でしょう。 Ruby では find_all というメソッド名です。
x.find_all {|e| e > 2}
のように書きます。Groovy は Ruby に倣いつつも基本は Java 言語ですから,Java の流儀に従い,これを findAll としています。
ちなみに Ruby がとりわけ collection 関係で倣っている Smalltalk では,これを
x select: [ :each | each > 2 ]
のように書きます。予想されるとおり,Ruby は find_all の同義語として select も用意しています。
x.select {|e| e > 2}
しかし,Groovy はこちらは用意していません。 Ruby のコレクション機能は Smalltalk から多くを継いでいますが,Groovy のそれは Smalltalk 由来というよりは Ruby 由来というわけです。

Ruby の Enumerable モジュールに直接相当するものは Groovy にはない。 文字列や配列を含めて概念的に集合体を表すあらゆるクラスに共通のインタフェースというものが Java に欠如していることから,Groovy は不可避的に each, collect, findAll, grep などのコレクション用メソッドを,Java の基礎的な列挙操作を担う iterator メソッドとともに (一見すると乱暴に思えるが) java.lang.Object に追加している。 個々のクラスで iterator メソッドを適切に再定義しさえすれば,一群のコレクション用メソッドが自動的に利用可能になる。

java.lang.Object に追加されたデフォルトの iterator() はレシーバ自身を1個列挙するだけの java.util.Iterator インスタンスを作ります。

こうして Groovy は,本来の Java では不可能だった文字列や配列を含めたあらゆる集合体オブジェクトに対する一様な操作を Ruby と同じように実現している。

groovy:000> "Groovy".collect{ it.toUpperCase() }.join("-")
===> G-R-O-O-V-Y
groovy:000> for (x in "Groovy") println x
G
r
o
o
v
y
===> null
groovy:000>  

"Groovy" という単語がプログラミング言語の名前としてふさわしいかどうかは自明ではないが,もしも君が Java 言語とその標準クラス・ライブラリの設計の一様性の欠如にうんざりしていたならば (そして言語の互換性の観点から,その状況が改善されないだろうと絶望していたならば),Groovy のこの側面を知ったとき,思わず "Groovy!" とつぶやいたとしても無理からぬことだろう。

6 スクリプトとしてのプログラムと暗黒面

今さらながら,ここで改めて Ruby や Python と同じように Groovy の対話シェルでも 4.1 節 の階乗関数 factorial(int n) の例のように,クラスで囲まずに関数を定義できることに注意しよう。

groovy:000> BigInteger factorial(int n) {
groovy:001>   if (n == 1)
groovy:002>     1
groovy:003>   else
groovy:004>     n * factorial(n - 1)
groovy:005> }

2.3 節2.5 節 で試してきたような Groovy スクリプトでも,このように関数をじかに定義できる。

一見すると,こうして定義した関数は,クラスから独立しているように見える。 しかし,一歩実装に踏み込めば,これもやはりクラスに属するメソッドであり,さらに言えば this を必要とする普通の非静的メソッドである。 この関数の実行時に Groovy が暗黙裏に与える this の値は,対話シェルやスクリプトの実行文脈として Groovy が自動生成する groovy.lang.Script 派生クラスのインスタンスである。 こうして Java と同じ動作モデルのもとで,スクリプト言語としての体裁を保っている。

しかし,このような実装による取り繕いは完全ではなく,いくつか言語の暗黒面としてほころびている。 本章ではこれを噛み砕いて説明する。

6.1 トップレベルと Script クラス

Groovy のような入れ子の構造をもった言語で,他の入れ子になっていない地の部分をしばしばトップレベル (top level) と呼ぶ。

「地」なのにトップとはこれ如何に? と思うのは当然の疑問です。 これは,そこから何かを積み上げるのか,それとも掘り下げるのかのイメージの違いです。 地の部分として考えるよりもむしろ,プログラム・リストで字下げのない部分ととらえるとよいかもしれません。 技術用語としての「トップレベル」の最初の用例はおそらくは Lisp の対話環境です。 半世紀の歴史と伝統がある用語です。

Groovy は Java を直接の基盤とする言語でありながら,クラスに囲まれないこの場所で関数を定義でき,変数も定義できる。 それらの定義の Java から見た位置付けはどうなっているのだろうか。 groovy.lang.Script クラスと,暗黙裏に自動生成されるその派生クラスがその鍵である。

Groovy は,任意のファイル名 S.groovy のスクリプトに対して, groovy.lang.Script の派生クラスとして,スクリプトのファイル名にちなんだクラス S を暗黙裏に定義する。 対話シェルでも同様の派生クラスを,処理系実装が決めたクラス名で暗黙裏に定義する。

君がトップレベルで任意の関数 f を定義したとき,Groovy はそれをこの暗黙裏のクラスの非静的メソッド f として解釈する。 くだんの階乗関数 factorial(int n) はこれに当てはまる。

同様にトップレベルで実行文を書いたとき,それはこの暗黙裏のクラスが自動的に定義する非静的メソッド run() の実行文となる。

トップレベルで変数を宣言すると,それはこの run() メソッドのローカル変数となる。

だから君が対話シェルで

groovy:000> int i; for (i = 0; i < 3; i++) { println i }; i

と打ち込んだとき,Groovy 内部では暗黙裏の groovy.lang.Script 派生クラスの非静的メソッド run() として,大まかに言えば

@Override public Object run() {
  int i;
  for (i = 0; i < 3; i++) { DefaultGroovyMethods.println(i); };
  return Integer.valueOf(i);
}

のようなものが自動的に定義され実行されている (DefaultGroovyMethods4.3 節 参照)。

同様に,2.3 節のスクリプト・ファイル hello.groovy はたった1行
println "hello, world"
を含んでいるだけですが,Groovy ではこれを,大まかに言えば次のような Java のクラス定義として解釈します。
import groovy.lang.Script;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;

public class hello extends Script
{
    @Override public Object run() {
        DefaultGroovyMethods.println(this, "hello, wolrd");
    }

    public static void main(String[] args) {
        Script s = new hello ();
        s.run();
    }
}
実際,hello.groovy はあたかもクラス hello を定義しているファイルであるかのように対話シェルから使うこともできます。
groovy:000> import hello
===> [import hello]
groovy:000> h = new hello()
===> hello@ec71f8
groovy:000> h.run()
hello, world
===> null

6.2 ファイル名と同名のクラスを定義するスクリプト

もしもファイル名 S.groovy のスクリプトが同じ名前のクラス S を陽に定義しているならば,(同じくS という名前になるはずだった) groovy.lang.Script の暗黙裏の派生クラスは定義されない。

したがって,この場合はスクリプトのトップレベルに文や変数宣言を置くことはできない。

これが 2.5 節 の注意書きの理由です。

6.3 対話シェルでの変数宣言

はじめに,対話シェルの各行がそれぞれ groovy.lang.Script の暗黙裏の派生クラスの非静的メソッド run() としてそのつど実行されていることを確かめよう。

groovy:000> this
===> groovysh_evaluate@1a9883d
groovy:000> this.getClass()
===> class groovysh_evaluate
groovy:000> this.getClass().getSuperclass()
===> class groovy.lang.Script
groovy:000> run()
ERROR java.lang.StackOverflowError:
null
        at groovysh_evaluate.run (groovysh_evaluate)
        ...
groovy:000>  

このように thisgroovy.lang.Script の派生クラスのインスタンスであり, run() メソッドを実行すると,無限再帰によってスタックあふれのエラーを起こす。

もし興味があれば,無引数の run() 関数を定義しようとするとエラーが発生することも確かめてみてください。

トップレベルで宣言された変数は,このような run() メソッドのローカル変数として実現される。 その帰結として対話シェルには次の重大な注意事項がある: トップレベルで宣言された変数はその1行限りの実行で消える。

groovy:000> int s = 0xFF; println s; println s.getClass()
255
class java.lang.Integer
===> null
groovy:000> s
ERROR groovy.lang.MissingPropertyException:
No such property: s for class: groovysh_evaluate
        at groovysh_evaluate.run (groovysh_evaluate:2)
        ...
これに対し,対話シェルのトップレベルで定義した関数は,そのシェルの実行中ずっと有効です。 これには少しからくりがあります。
対話シェルでは1行ごとに this のクラスのアイデンティティが異なります。
groovy:000> System.identityHashCode(this.getClass())
===> 27507852
groovy:000> System.identityHashCode(this.getClass())
===> 30675736
つまり,暗黙裏のクラスは,普通は,入力された1行に対する run() メソッドを定義するためにそのつど作られ,そのインスタンスが this として構築され,run() の実行後に捨てられます。 しかるに関数を定義したときは,そのときの this をくるんだクロージャを作って binding variable (後述)とします。 ですから,
groovy:000> Object foo() { this }
===> true
groovy:000> foo()
===> groovysh_evaluate@4fdf11
groovy:000> foo()
===> groovysh_evaluate@4fdf11
groovy:000> this
===> groovysh_evaluate@4d28c7
groovy:000> this
===> groovysh_evaluate@a30fd
groovy:000> foo()
===> groovysh_evaluate@4fdf11
のように,対話シェルのトップレベルで定義された関数はいつも同じ this を参照します。 そしてその this とは,その関数自身がメソッドとして定義された当時の暗黙裏のクラスのインスタンスです。 ですから,
groovy:000> Object bar() { this.getClass().getMethod("bar", null) }
===> true
groovy:000> bar()
===> public java.lang.Object groovysh_evaluate.bar()
groovy:000> this.getClass().getMethod("bar", null)
ERROR java.lang.NoSuchMethodException:
groovysh_evaluate.bar()
        at groovysh_evaluate.run (groovysh_evaluate:2)
        ...
のように,関数内部で this のクラスにその関数と同じ名前のメソッドが定義されているか尋ねると,その関数を実装するメソッドの参照を返します。 しかし,関数外で同じことを尋ねても No Such Method 例外に終わります。

6.4 binding variable

トップレベル,あるいはトップレベルでの関数定義の中で,宣言なしにいきなり変数に値を代入したとき,エラーとはならずに暗黙裏のクラスの Object 型の非静的 public フィールドであるかのように振舞うものとして解釈される。 これを binding variable と呼ぶ。

binding variable はトップレベルと,トップレベルで定義された関数内で,事実上のグローバル変数となる。

def foo() { x = 0 }
def bar() { x++; return x }

foo()
println bar()
println bar()
println bar()

このスクリプトを実行した例を示す。

01:~/tmp$ groovy darkside.groovy
1
2
3
01:~/tmp$  

ちなみに,トップレベルではなくクラス内のメソッド定義の中で,宣言なしにいきなり変数に値を代入したときは MissingPropertyException 例外が実行時に発生する。

これは Ruby や Python に慣れた人にとっては深刻な落とし穴です。 トップレベルに関していえば,スクリプト言語として振舞うために binding variable のような機構は必要ですが,それを関数内での変数代入でも新規作成できなければならない理由はありません。 言語設計上の不可避な制約ではなく,JavaScript の悪名高い過ちを再び繰り返した人為的な誤りとも言えます。

少なくとも Ruby ファンにとっては,この一点だけで Groovy は別種の言語だと感じてしまう残念な箇所です……。

実際には,binding variable は暗黙裏のクラスの非静的 public フィールドではなく,binding と呼ぶ連想配列により実現される。 対話シェル groovysh で1行実行するごとに新しく作られる暗黙裏のクラスのインスタンスは, 構築時にこの連想配列を引き継ぐことにより,それ以前の行で作成された binding variable を参照する。

したがって,binding variable として a を作成したとき,これを getClass().getField("a") で参照しようとしても java.lang.NoSuchFieldException に終わります。 getProperty("a") で値を得たり,setProperty("a", "Groovy") のように値をセットしたりすることはできます。
groovy:000> a = "Java"; getClass().getField("a")
ERROR java.lang.NoSuchFieldException:
a
        at java_lang_Class$getField.call (Unknown Source)
        at groovysh_evaluate.run (groovysh_evaluate:2)
        ...
groovy:000> getProperty("a")
===> Java
groovy:000> setProperty("a", "Groovy")
===> null
groovy:000> a
===> Groovy
groovy:000> getProperty("a")
===> Groovy
つまり,もしも暗黙裏のクラスの binding variable を Java から直接参照したいときは, groovy.lang.Script#getProperty(String)groovy.lang.Script#setProperty(String, Object) のメソッドを使うことになります。

連想配列そのものは groovy.lang.Script#getBinding() で得ることができます。
groovy:000> getBinding()
===> groovy.lang.Binding@9ca1fb
groovy:000> getBinding().getVariable("a")
===> Groovy
groovy:000> getBinding().getVariable("a").is(a)
===> true
getBinding().getVariable("a") は binding variable である a の値を得るための遠回りの方法です。

これに関連して,対話シェルのトップレベルで定義した関数の中で binding variable を使う場合は特に注意が必要である。 現在の実装では,そういった関数が参照する連想配列 (厳密に言えば,その関数をメソッドとして持つ暗黙裏の groovy.lang.Script 派生クラスのインスタンスが持つ binding) は,現行の連想配列とは独立している。 したがって下記のような現象が起こる。

groovy:000> a = 123
===> 123
groovy:000> def f() { x = 456; getBinding().getVariables() }
===> true
groovy:000> f()
===> {x=456}
groovy:000> x
ERROR groovy.lang.MissingPropertyException:
No such property: x for class: groovysh_evaluate
        at groovysh_evaluate.run (groovysh_evaluate:2)
        ...
groovy:000>  

f() を実行したとき,確かに binding variable として x が 456 にセットされる。 しかし,そのとき使われる連想配列は事実上 f() だけが使う連想配列である。 あるはずの a=123 はそこにはない。 逆に,現行の連想配列に x はない。

このことの帰結として,対話シェルのトップレベルで定義した関数どうしは,互いを呼び出すことができません。 暗黙裏のクラスが使い捨てにされる対話シェルで,トップレベルで定義した関数が引き続き使えるのは,それが binding variable として参照されるからです。 しかし,それらの関数本体では,それぞれ独立した連想配列が使われますから,互いを参照することができないわけです (ただし,自分自身だけは暗黙裏のクラスのメソッドとして参照することができます)。

6.5 入れ子でない可視性

これまで説明したように Groovy はスクリプト言語らしく振舞うため,トップレベルでの文や宣言を許している。 しかし,それらを groovy.lang.Script の派生クラスのメソッドや,そのローカル変数,あるいはプロパティとして実現している。 ここに言語の大きな歪みがある。

トップレベルの文や宣言は,字面の上では,クラス定義など他の構成要素を入れ子として取り囲む位置にある。 入れ子の構造をもった言語では,上位の構造で宣言された変数や関数が,下位の構造から可視であることが,伝統と慣習から一般に強く期待されている。 しかし,Groovy ではトップレベルの文や宣言が,実装上,他のクラス定義と同格の groovy.lang.Script 派生クラスの中に,あるいはそのメソッドの中に収容されるため,この期待を裏切る結果になる。

ここでそれをまとめよう。

  1. トップレベルで宣言した変数は,どの関数からも不可視である。
  2. トップレベルで定義した関数と binding variable は,クラス内から不可視である。
    対話シェルの場合,さらに加えて,トップレベルで定義した他の関数からも不可視である。

例えば Groovy スクリプト test1.groovy

class A {
  def foo() { 100 }
}

class B {
  def bar() { 200 }
}

def baz(x) {
  println "<" + x + ">"
}

A a = new A()
baz(a.foo())
b = new B()
baz(b.bar())

のように与えられているとき,実装の詳細を省いて大まかに言えば groovy はこれを次のように解釈する。

class A {
  def foo() { 100 }
}

class B {
  def bar() { 200 }
}

class test1 extends Script {
  def baz(x) {
    println "<" + x + ">"
  }

  @Override def run() {
    A a = new A();
    baz(a.foo());
    setProperty("b", new B());
    baz(getProperty("b").bar());
  }

  static void main(String[] args) {
    Script s = new test1();
    s.run();
  }
}

両者を見比べて,トップレベルの関数や変数の可視性がどのように制限されるかを確かめよう。

ちなみに言えば,書換え後の内容はトップレベルの文がなく,しかも複数のクラスが定義されているため,そのまま実際に groovy コマンドで実行することができない。 しかし,2.4 節 のようにコンパイルすれば java で実行できる。

01:~/tmp$ groovyc test1.groovy
01:~/tmp$ ls *.class
A.class         B.class         test1.class
01:~/tmp$ java -cp ".:/usr/local/share/groovy-2.0.1/embeddable/groovy-all-2.0.1.
jar" test1
<100>
<200>
01:~/tmp$  

現実の本格的な Groovy プログラミングでは,ほとんどのコードがクラス定義の中に書かれることになるから,トップレベルの実装方法に由来する奇妙な振舞はそれほど問題とはならない。 しかし,知らなければ,君は遅かれ早かれこれらの振舞に直面して,わけの分からない非直感的な言語だと Groovy を嫌悪することにもなりかねない。 それが,本章であえて言語のさまざまな暗黒面を説明した理由である。

参考のため,トップレベルで定義した関数を,クラスのメソッドの中から呼び出す技巧的な方法を説明します。
class A {
  int foo(x) { bar() + x }
}

int bar() { 10 }

a = new A ()
println a.foo(1)        // ==> (groovy.lang.MissingMethodException 発生)
というスクリプトを考えます。 実際にこれを実行すると,メソッド foo の中から関数 bar を見つけられずに例外が発生します。 そうならないように関数 bar を呼び出すには,bar をメソッドとして扱い,レシーバを陽に与えます。 レシーバにはトップレベルでの this の値を与えます。 つまり,次のように書き換えます。
class A {
  int foo(x, self) { self.bar() + x }
}

int bar() { 10 }

a = new A ()
println a.foo(1, this)  // ==> 11
あくまで参考のために説明しました。 実際には,このように実装詳細に土足で踏み込むようなことはなるべく避けるべきです。

7. おわりに

完全な説明には遠いが,基礎として十分な程度には Groovy の言語仕様を君に伝えた。 Groovy には,広く普及した Java の知識と経験をいかすことができ,既存の Java システムに組み込むことができる即戦力の道具としての側面と,草創期からの Ruby にあった複雑さを切り捨て,その良いアイディアから新しく構成した軽量言語としての側面がある。 現実の大多数のプログラミング言語がそうであるように決して闇の部分がないわけではないが,それでもただ敬遠するには惜しいことが今の君には理解できると思う。

とりわけその大仰な名前で損をしている面が大きそうですが,もしもコマンド名とファイル接尾辞を gy (対話シェルは gysh) ぐらいにしていたら,あるいは言語名を gjava ぐらいにしていたら,案外,すぐに普及したのかもしれません。 もしも groovy [grú:vi] という単語が ruby [rú:bi] と発音をそろえるためにあえて選択されたのだとしたら, もしかしたら,日本ではあえて「るびー」と呼ぶのが本来の意図にあっているのかもしれません。

8. 参考文献

  1. Groovy - Home: http://groovy.codehaus.org
  2. D. König et al. (著),関谷 et al. (訳):「Groovy イン・アクション」, 毎日コミュニケーションズ, 2008, ISBN 978-4-8399-2727-1
  3. 関谷 et al.:「プログラミング Groovy」, 技術評論社, 2011, ISBN 978-4-7741-4727-7

文献 1. は Groovy の公式ページである。 実際の Groovy プログラミングでは,とりわけ 4.3 節 で紹介した Groovy JDK API Specification [codehaus.org] が基礎的な資料として重要である。

文献 2. は,日本語で読むことができる Groovy のまとまった資料である。 ていねいな記述と翻訳は,本来ならば良書と評価すべき質である。 しかし,この本には索引が貧弱で,ほとんど役立たないという欠陥がある。 実際のプログラミングで参照するために,読者は,約 600 ページの大部の本のどこに何が書かれているか記憶する必要がある。 検索可能な電子化文書として発行されるか,あるいは索引が増補されることが望まれる。

文献 3. は,新しく日本語で書き下ろされた,よりコンパクトな本である。 プログラム・コードの行間が空きすぎて読みにくいきらいがあり,ページ数の制約からかもう一歩説明が足りないところがあるが,広範な Groovy のトピックを分かりやすく手引きしている。 近年の Groovy の様々な側面を知り,Groovy を効果的に活用するための必読書といってよい。

付録 1. Emacs 編集モード

テキスト・エディタ Emacs で Groovy のコードを編集するためのモードを実現する groovy-mode.el は下記から入手できる。

Groovy Emacs Mode [launchpad.net]

2012年 2月 3日現在の現行版は emacs-groovy-mode_2011-06-29.tgz である。 展開するといくつかファイルがあるが,さしあたり必要なのは groovy-mode.el である。 groovy-mode.el を Emacs Lisp の load-path が通っている場所 (例えば,/usr/share/emacs/site-lisp~/Applications/Emacs.app/Contents/Resources/site-lisp など) に置き,~/.emacs ファイル (または ~/.emacs.d/init.el など相当するファイル) に下記を追加する。

(autoload 'groovy-mode "groovy-mode" "Groovy editing mode." t)
(add-to-list 'auto-mode-alist '("\\.groovy$" . groovy-mode))
(add-to-list 'interpreter-mode-alist '("groovy" . groovy-mode))

1行目は Emacs Lisp 関数として groovy-mode が呼び出されたとき, "groovy-mode" という名前に拡張子をつけてファイルを読み込むようにする。

2行目は .groovy という拡張子のファイルを編集するとき, groovy-mode を呼び出して編集モードを切り替えるようにする。

3行目は #! ではじまるスクリプトを編集するとき, 指定されているインタープリタ名が groovy ならば (例: #!/usr/bin/env groovy), groovy-mode を呼び出して編集モードを切り替えるようにする。

もしも Emacs に閉じ括弧などの自動挿入をさせたいならば, groovy-mode.el と同じ場所に groovy-electric.el を置き, ~/.emacs ファイルまたは相当するファイルに下記を追加する。

(add-hook 'groovy-mode-hook (lambda ()
                              (require 'groovy-electric)
                              (groovy-electric-mode)))

キーワードなどに対する色や書体の指定は,他の編集モードと共通である。 下記に ~/.emacs ファイルでのカスタマイズの一例を示す。

(if window-system
    (progn
      (set-face-foreground font-lock-variable-name-face "black")
      (set-face-foreground font-lock-keyword-face "NavyBlue")
      (set-face-foreground font-lock-function-name-face "MediumBlue")
      (set-face-foreground font-lock-type-face "DeepSkyBlue4")
      (set-face-foreground font-lock-string-face "DarkGreen")
      (set-face-foreground font-lock-builtin-face "black")
      (make-face-italic font-lock-variable-name-face)
      (make-face-italic font-lock-comment-face)
      (make-face-bold font-lock-keyword-face)
      (make-face-bold font-lock-builtin-face))
  (set-face-foreground font-lock-keyword-face "blue")
  (set-face-foreground font-lock-function-name-face "black")
  (set-face-foreground font-lock-variable-name-face "black")
  (make-face-bold font-lock-variable-name-face)
  (make-face-bold font-lock-type-face)
  (set-face-foreground font-lock-type-face "black")
  (set-face-foreground font-lock-comment-face "red"))
このサイトにある各プログラミング言語の色付きリスティングの大半は,この設定による Emacs (window-system モード) から M-x htmlize-buffer をして,作成しています。

付録 2. NetBeans プラグイン

統合開発環境 NetBeans [netbeans.org] は Groovy を標準でサポートしている。 ダウンロードバンドルによっては NetBeans 本体とともに Groovy 用のプラグイン「Groovy および Grails」をインストールできるが,そうでない場合も後からネットワーク経由で追加できる (「ツール」 → 「プラグイン」を選ぶ)。

「Groovy および Grails」の Grails [grails.org] とは,Ruby に対する Rails に相当します。 語義どおりの (探し求めるべき) 聖杯と,Groovy RAILS とをかけた名前です。

プラグインをインストールした NetBeans 上で Groovy をとりあえず試すには「ファイル」→「新規プロジェクト」からカテゴリ「サンプル」 の「Groovy」にある「Groovy-Java のデモ」を選ぶ。 サンプル・プロジェクト「GroovyJavaDemo」が作られる。

ソース・ファイルは DisplayDialog.javaDisplayProvider.groovy の二つである。 後者のコードは 4 行しかないが,Groovy でアクセス制御を指定せずに定義したフィールドが Java から普通の Java Beans プロパティとして利用できる実例となっている (4.4 節)。


NetBeans のライバル Eclipse [eclipse.org] については Groovy 本家ページ [codehaus.org] からプラグインがリリースされています。 参考文献 3.「プログラミング Groovy」の巻末の付録にその丁寧な解説があります。


目次へ


Copyright (c) 2009 - 2012 OKI Software Co., Ltd.