Groovy 入門

2009.3.19 - 2012.2.3 (鈴)
2009.4.16 時点のページ

1. はじめに

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

本稿は,Java の知識を前提として Groovy 言語の概要を紹介する。 Ruby の知識は必須ではないが,知っていれば,より 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年 2月 3日現在の最新安定版は 2011年 12月 23日にリリースされた Groovy 1.8.5 の groovy-binary-1.8.5.zip である。

リリース日は日本では天皇誕生日にあたりますが,サーバ上の zip のタイムスタンプは日本で日付がその翌日にかわるころの時刻になっています。 その心はおそらく世界のどこでもクリスマスまであと一日はあるように間に合わせたのでしょう。 リリース文 [codehaus.org] の結びの言葉は "Groovy Christmas and a very Happy New Year" でした。

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

$ unzip ~/Downloads/groovy-binary-1.8.5.zip
Archive:  /Users/suzuki/Downloads/groovy-binary-1.8.5.zip
The Groovy 1.8.5 binary distribution.
   creating: groovy-1.8.5/
   creating: groovy-1.8.5/bin/
  inflating: groovy-1.8.5/bin/grape  
  inflating: groovy-1.8.5/bin/groovy  
  inflating: groovy-1.8.5/bin/groovyConsole  
  inflating: groovy-1.8.5/bin/groovyc  
  inflating: groovy-1.8.5/bin/groovydoc  
  inflating: groovy-1.8.5/bin/groovysh  
  inflating: groovy-1.8.5/bin/java2groovy  
  inflating: groovy-1.8.5/bin/grape.bat  
  inflating: groovy-1.8.5/bin/groovy.bat  
  inflating: groovy-1.8.5/bin/groovy.icns  
  inflating: groovy-1.8.5/bin/groovy.ico  
  inflating: groovy-1.8.5/bin/groovyConsole.bat  
  inflating: groovy-1.8.5/bin/groovyc.bat  
  inflating: groovy-1.8.5/bin/groovydoc.bat  
  inflating: groovy-1.8.5/bin/groovysh.bat  
  inflating: groovy-1.8.5/bin/java2groovy.bat  
  inflating: groovy-1.8.5/bin/startGroovy  
  inflating: groovy-1.8.5/bin/startGroovy.bat  
   creating: groovy-1.8.5/conf/
   creating: groovy-1.8.5/embeddable/
   creating: groovy-1.8.5/lib/
  inflating: groovy-1.8.5/ANTLR-LICENSE.txt  
  inflating: groovy-1.8.5/ASM-LICENSE.txt  
  inflating: groovy-1.8.5/CLI-LICENSE.txt  
  inflating: groovy-1.8.5/JSR223-LICENSE.txt  
  inflating: groovy-1.8.5/LICENSE.txt  
  inflating: groovy-1.8.5/NOTICE.txt  
  inflating: groovy-1.8.5/conf/groovy-starter.conf  
  inflating: groovy-1.8.5/embeddable/groovy-all-1.8.5.jar  
  inflating: groovy-1.8.5/lib/ant-1.8.2.jar  
  inflating: groovy-1.8.5/lib/ant-antlr-1.8.2.jar  
  inflating: groovy-1.8.5/lib/ant-junit-1.8.2.jar  
  inflating: groovy-1.8.5/lib/ant-launcher-1.8.2.jar  
  inflating: groovy-1.8.5/lib/antlr-2.7.7.jar  
  inflating: groovy-1.8.5/lib/asm-3.2.jar  
  inflating: groovy-1.8.5/lib/asm-analysis-3.2.jar  
  inflating: groovy-1.8.5/lib/asm-commons-3.2.jar  
  inflating: groovy-1.8.5/lib/asm-tree-3.2.jar  
  inflating: groovy-1.8.5/lib/asm-util-3.2.jar  
  inflating: groovy-1.8.5/lib/bsf-2.4.0.jar  
  inflating: groovy-1.8.5/lib/commons-cli-1.2.jar  
  inflating: groovy-1.8.5/lib/commons-logging-1.1.1.jar  
  inflating: groovy-1.8.5/lib/extra166y-1.7.0.jar  
  inflating: groovy-1.8.5/lib/gpars-0.12.jar  
  inflating: groovy-1.8.5/lib/groovy-1.8.5.jar  
  inflating: groovy-1.8.5/lib/hamcrest-core-1.1.jar  
  inflating: groovy-1.8.5/lib/ivy-2.2.0.jar  
  inflating: groovy-1.8.5/lib/jansi-1.7.jar  
  inflating: groovy-1.8.5/lib/jline-0.9.94.jar  
  inflating: groovy-1.8.5/lib/jsp-api-2.0.jar  
  inflating: groovy-1.8.5/lib/jsr166y-1.7.0.jar  
  inflating: groovy-1.8.5/lib/junit-4.10.jar  
  inflating: groovy-1.8.5/lib/servlet-api-2.4.jar  
  inflating: groovy-1.8.5/lib/xmlpull-1.1.3.1.jar  
  inflating: groovy-1.8.5/lib/xstream-1.4.1.jar  
$ cd groovy-1.8.5/bin
$ ./groovysh
Groovy Shell (1.8.5, JVM: 1.6.0_29)
Type 'help' or '\h' for help.
-------------------------------------------------------------------------------
groovy:000> 

対話シェルでは 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
$ 

この例では,

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

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

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

Groovy 言語では,他の式の一部分ではない式,つまり最外の式に限り,引数付きメソッド呼出しの丸括弧を省略してよい。 ここでは println の最初の呼出しで丸括弧を省略している。

省略せずに書くと println("hello, world") です。 これを対話シェルに与えて,println "hello, world" と同じになることを確かめてください。
Ruby ファンへの警告: メソッド呼出しの丸括弧を省略できるのは,呼出し自体が最外の (つまり,文レベルの) 式であって,1個以上の引数付きの場合だけです。 他の式の部分式ならば省略できませんし,無引数呼出しでも省略できません。 もしも無引数呼出しで丸括弧を省略した場合,もしあればコマンド, なければメソッドとは別の名前空間にあるプロパティやフィールドが参照されます。
ですから,もしも println() のつもりで println とだけ入力すると, No such property (そのようなプロパティはない) と叱られます。

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

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

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

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

注意深い人は,この時点で自分のホーム・ディレクトリに ~/.groovy というディレクトリが作られていることに気付くと思います。 このディレクトリには groovysh.history というファイルが作られています。 これは対話シェル groovysh が入力行を記録したテキスト・ファイルです。 これにより,次回また対話シェルを使うとき,上向き矢印キーや Control-P の打鍵ですぐに前回の入力を取り戻せます。

2.2 インストールしよう

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

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

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

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

$ type groovysh
groovysh is /usr/local/bin/groovysh
$ ls -l /usr/local/bin/groovysh
lrwxr-xr-x  1 root  admin  34 Feb  2 10:01 /usr/local/bin/groovysh -> ../share/g
roovy-1.8.5/bin/groovysh

さしあたり同様にして,非対話型インタープリタ groovy とコンパイラ groovyc のシンボリック・リンクも作ろう。

もしかしたらコマンド名の解釈のされ方を確認する方法として type ではなく which を使うことを学んでいるかもしれません。 zsh ならばそれも許容範囲ですが,もし bash を使っているならば,お気の毒ながら,それは正しくありません。 入力されたコマンドをシェルがどう解釈するのか,正確なところは当事者であるシェル自身に尋ねるしかありませんが,bash にとって which は外部のコマンド /usr/bin/which です。 type name と type -a name を使うことをおぼえてください。

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

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

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

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

$ groovy hello.groovy
hello, world
$ 
Groovy がこの1行のスクリプトを内部で大体どのように解釈して実行しているのか,結論だけ知りたければ 4.3.2 節 を見てください。 Groovy がどのような性格の言語であり,どのような使い道がありそうか,人によってはこれだけでほぼ把握できるはずです。

2.4 groovyc で Java クラスを作ってみよう

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

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

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

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

$ groovyc Greeting.groovy
$

これを Java プログラムから呼び出してみよう。

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

import com.example.proj.Greeting;

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

同じディレクトリでコンパイルする。

$ javac Main.java
$

実行するには,カレント・ディレクトリと groovy-1.8.5/embeddable/groovy-all-1.8.5.jar を CLASSPATH に含める。 この jar ファイルは groovy の処理系一式のクラス・ファイルを含んでいる。

$ java -cp ".:/usr/local/share/groovy-1.8.5/embeddable/groovy-all-1.8.5.jar" Main
hello, world
$

2.5 ここまでと,これから

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

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

groovy:000> System.out.println("hello, world");
hello, world
===> null
groovy:000> 

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

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

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

3.30 分で分かる Groovy 速成講座

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

本稿の初期版では「3分で分かる」と題していました。 ざっと目を通すならその程度で終わります。30 分は考え込む余裕を見込んだ長さです。
  1. 妥当な Java プログラムの大半はそのまま妥当な Groovy プログラムである。
    したがって Java プログラマならば,誰でもすぐに Groovy プログラミングが可能です。
  2. どの Java クラス・ライブラリも Groovy から利用できる。 Java と同じく,環境変数 CLASSPATH に入れて import 可能にできる。
    下記はあらかじめ import 済みです。陽に import しなくても構いません。
    java.io.*
    java.lang.*
    java.math.BigDecimal
    java.math.BigInteger
    java.net.*
    java.util.*
    groovy.lang.*
    groovy.util.*
  3. ファイルの接尾辞は .groovy である。 このことを除き, 妥当な Java プログラムのファイル編成は,そのまま妥当な Groovy プログラムのファイル編成である。
    例えば package com.example.proj と宣言されたクラス Foo のソースを, ディレクトリ com/example/proj にファイル Foo.groovy として置けば, 他の Groovy プログラムから import com.example.proj.Foo として import できます。
    ソースのかわりに,groovyc でコンパイルした Foo.class を同じ場所においても構いません。 この場合は Java プログラムからもクラス com.example.proj.Foo を import できます。 2.4 節 を見直してください。

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

  1. プリミティブ型か参照型かにかかわらず,a == b は値の一致を判定する (a != b は値の不一致を判定する)。
    参照型のアイデンティティの一致は a.is(b) で判定する。
    Groovy では,a == ba.equals(b) は,数値を比較するときに算術変換を行うかどうかだけが異なります。

    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
    
    別々のインスタンスでも == で等しいと判定されることに注意してください。 算術変換を使うことを除けば,Groovy の ==equals メソッドと同じです。 さらに言えば,new Integer(1) あるいは Integer.valueOf(1) と書く必要は実はありません。 単に 1 と書いたとき,実際には Java の Integer.valueOf(1) を意味します。
  2. charint などのプリミティブ型は,原則として java.lang.Characterjava.lang.Integer などのラッパー・クラスのインスタンスで表現される。 ただし,普段はこのことを意識する必要はない。 プログラムでは普通に charint として宣言して扱うことができる。
    前述のように == (および !=) はアイデンティティではなく値の一致 (および不一致) を判定する演算子として振舞います。他の演算子も,その値に対して,必要ならば算術変換を伴って演算をします。 可能ならばプリミティブ型のまま演算される場合もあります。 プリミティブ型を扱う Java のクラス・ライブラリとのあいだでは引数と戻り値が透過的に変換されます。
  3. a.equals(b)a.is(b) は レシーバ anull のときも正しく働く。 null は事実上,1個のインスタンスとみなされる。
    名目上,null.equals(b)null.is(b) はそれぞれ null と b が同じ値かどうか,および,同じアイデンティティかどうかを判定します。 名目は異なりますが,どちらも結局,単に b が null かどうかを判定します。 Groovy の null は Java の null とたしかに同一ですが, Groovy では擬似的に org.codehaus.groovy.runtime.NullObject の唯一のインスタンス,つまりシングルトンとして扱います。 したがって null.toString()null.getClass() などのメソッド呼出しもできます。
    Groovy の == とは算術変換を伴う equals であるというルールが,null をインスタンス扱いすることによって,簡単になっていることに注意してください。 説明に場合分けが不要になっています。 これと同様に,実際のコーディングでも null に対する特別な場合分けの大半を省くことができます。
  4. 文字列定数は,単一引用符で囲んでも,二重引用符で囲んでもよい。 char 定数を得たいときは文字列から型変換して作る。
    例: (char) 'a' または 'a' as char
    独自の演算子 as を使ったほうが,読むときに Java とは異なる型変換として認識しやすい点でいくらか好ましい。
    二重引用符で囲んだ場合は,Unix のシェルと同じように $ 記号で変数展開ができます。 例: "hello, $name"
    (型名) によるキャスト演算と as 演算は,基本的には同じですが,長さ 2 以上の文字列の char への変換で結果が異なります。 (型名) によるキャスト演算は例外を送出します。 as 演算は最初の文字の char 値を返します。 一般に (型名) によるキャスト演算は,異議のありそうな変換に対して,より保守的です。
    C# ファンへの警告: Groovy の as は C# の同名の演算子とは似て非なるものです。 "abc" as java.util.Map のように必ずしも直感的でない変換を大胆に行う場合もあれば, "abc" as Thread のようにキャスト失敗の例外を送出する場合もあります。 C# での (型名) キャストと as の使い分けは Groovy には通用しません。
  5. 任意の式を boolean にキャストすることができる。 このキャスト演算は (boolean 変数への代入など) キャストが必要とされる箇所で暗黙裏に行われる。 null0, "", 空のコレクション (例: new ArrayList()), 空の配列 (例: new int[0]) は false と見なされる。
    自動的にキャストが行われる典型的な場所として, if (e) …while (e) … などのテスト式 e があります。 ただし,この場所にじかに代入演算をおくことは構文的に禁止されています。 こうして,同様の緩やかさをもった他の言語でよく起こってきた間違いを防止しています。 もしも本当に代入演算をおきたいときは,さらに括弧で囲みます。

    不可: 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.4 節 の Greeting.groovy の public キーワードは省略できます。
  8. クラスのフィールド Klass xyz にアクセス制御を指定しなかったときJava Beans プロパティとしてのゲッター public Klass getXyz() とセッター public void setXyz(Klass) およびフィールド private xyz自動的に定義される。 フィールドに対する Java 本来の無指定時のアクセス制御 (package private) は Groovy では普通には表現できない。
    フィールドを package private にしたいときは,@PackageScope アノテーションを使います。
    class A {
      @PackageScope
      int x;
    
      int y;
    }
    
    この例では java.lang.Object の派生クラスとして public class A が定義されます。 このクラスはアノテーションで指定されたとおりの package private な int x と, JavaBeans プロパティの実体となる private int y を持ちます。 JavaBeans プロパティとしてメソッド public int getY() と public void setY(int) が自動的に定義されます。
    実際の Java のコーディングでは,振舞が中途半端で使い方にセンスが要求される (つまり,不適切な選択の危険が増える) ためか, pakcage private はほとんど使われません。 Java の設計者にしてみれば,気の利いた省略時解釈を用意したはずが,実際にはほとんど使われずに期待外れなのかもしれません。 Groovy は Java の現実の使われ方を踏まえ,クラスのフィールドの最も普通の定義のしかたを省略時解釈にして,この空白地帯を再利用しています。
  9. メソッドの throws 宣言は Groovy からは無視される。 チェックされる例外と,チェックされない例外の区別は Groovy にはない。
    アクセス制御の指定と同じく,この宣言もクラスの情報として織り込まれますから,Groovy で定義したクラスを Java からアクセスするときは,この宣言が有効になります。 Groovy で定義したクラスのメソッドから例外を送出して Java で catch する場合は,忘れずに Groovy のメソッド定義で throws 宣言をしておきます。
  10. dowhile 文を使うことはできない。
    無限ループと if 文,break 文の組み合わせで代用できます。 下記は i = 1; do { println i; i++; } while (i <= 10); の置き換えです。
    groovy:000> i = 1; for (;;) { println i; i++; if (i > 10) break; }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ===> null
    
    この例では変数 i を突然使っていますが,対話シェルと,Groovy スクリプトのクラスの外側では,変数を宣言せずに使うことができます。 とりわけ対話シェルでは,もしも変数をわざわざ宣言すると,その1行でしか変数を使えなくなりますから,普通は宣言せずに使います。 4.3.3 節で説明します 。
  11. 拡張 for 文では,コロンのかわりに in を使う。 例: for (int i in someList) { … }
  12. 値の並びをカギ括弧で囲んで 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
    
  13. クラスによっては,代入時に自動的にキャスト演算が行われる。 java.lang.String 値へのキャストでは toString() メソッドが使われる。 java.util.ArrayList 値から配列変数へのキャストでは toArray メソッドに相当する演算がなされる。
    配列の初期化構文はありませんが,整数配列 ii を {1, 2, 3} と初期化するには,波括弧をカギ括弧にかえて int[] ii = [1, 2, 3] と書けば OK です。
  14. 変数やメソッドの型の不一致は,型変換の失敗として実行時に検出される。 メソッドはオブジェクトの動的な型に基づいて実行時にはじめて解決されるから, 未定義メソッドの呼出しも,メソッドの欠如として実行時に検出される。
    例えば,下記のコードは,問題なく groovyc でコンパイルすることができます。
    class A {}
    
    class B {}
    
    class C {
      static void main(String[] args) {
        A a = new B();
      }
    }
    
    しかし,いざ実行してみると,キャストに失敗して次の例外が発生します: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'B@1428ea' with class 'B' to class 'A'

    この失敗が「型の不一致」ではなく「型変換の失敗」であることに注意してください。 Groovy は式の値を実装内部で (基本的には) java.lang.Object の静的型として扱います。 それを変数に代入するとき,Object から変数の型へとキャストします。 本来の Java ならば静的な型検査の失敗として検出される誤りが Groovy では動的な型変換の失敗になります。

    プログラムの自明な誤りを早期に発見できないという意味で,これは Groovy の弱点ですが,ごく近い将来の Groovy ではオプショナルな静的型検査の導入によってこれが改善されます。 まだ正式版ではありませんが,Groovy 1.8.5 と同時にリリースされた Groovy 2.0.0 β2 では下記のように @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();
           ^
    
    一方,その逆に,Groovy はそもそも言語に可能な動的性質をまだ十分に極めているとはいえないきらいもあります。 特定のクラスへの暗黙の型変換を実行時の処理で実現しているのですから, その処理で利用者定義のメソッドを呼び出すようにすれば,暗黙の型変換自体を実行時に自由に変更できるはずです。 しかし,現実はそうなっていません。現在の実装では,上記 class A のような一般のクラスの変数へ代入するとき, 次のメソッドを使ってそのクラスへの暗黙の型変換が行われます: public static Object org.codehaus.groovy.runtime.ScriptBytecodeAdapter#castToType(Object, Class)

    今のところ,これは作り付けの動作しかできません。 しかし,このメソッドのソースを追って行くと TODO コメントとして暗黙の型変換を利用者定義可能にする考えが語られています。 このメソッドの実装は 2.0.0 β2 でもまだ現状と変わりませんが,もしも,この考えが実現すれば,Groovy は今以上に完全な動的言語になります。
  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: を省くと構文エラーになります。

    ラベルのないブロックを単体で使うことは日常的には滅多にありませんから, このチュートリアルを終えた後は,そんな制限もあったとおぼろげに覚えておくだけで十分です。 この制限は,クロージャの省略記法と構文的に区別するために必要とされます。

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

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

ここまでの知識でも,例えば,Java で書かれたクラスの private な内部に再コンパイルなしに自由にアクセスできるツールなどとして便利に使えますから,ここで切り上げても無駄にはなりません。 しかし,それではあまりにもったいないです。

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

4.1 セミコロンと return の省略

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

groovy:000> L1: { String s = 0xFF; println s }
255
===> null
groovy:000> L1: { String s = 0xFF; println s.getClass() }
class java.lang.String
===> null
普通はこのような単体のブロックは,ローカル変数の有効範囲を細かく区切りたいときに使います。 ここではブロック外に変数 s を残さず,毎回まっさらの変数を使う意図を込めています……が,実は対話シェルでは不要です。 有効範囲のためだけなら,ブロックとして囲む必要はありません。 (4.3.3 節 参照)。 上記の例はここでの説明と,前章最後の「ブロックに適当なラベルを付ける」実例を兼ねた,わざとらしいサンプルです。

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

groovy:000> 1 + (
groovy:001> 2 + 3)
===> 6
この機能は古いバージョンの Groovy に比べて控えめになっています。 かつては次のような自動継続が可能でしたが,今は構文エラーとされます。
groovy:000> 1 + 
groovy:001> 2
===> 6
Groovy 1.8.5 では1行目を入力した時点で groovysh_parse: 1: expecting EOF, found '+' @ line 1, column 3. と叱られます。

ところで, 最初の単体のブロックの例では ==> の先に結果の値として null が示されている。 これは正確には何だろうか?

2.1 節で最初に対話シェルを動かしたとき,println メソッドを実行して null が結果の値として得られたことを思い出そう。 単体ブロックを実行して得られた null は,実は,そのブロックの最後に実行された println メソッドの結果の値にほかならない。 ブロックで最後に実行された文の結果の値が,ブロック全体の結果の値になる。

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

厳密にいえば,このように return 文を省略可能にすることは,言語設計上の唯一の選択肢ではありません。 メソッド本体のブロックの結果の値と,メソッド呼出しの結果の値を別のものとして定義し,後者は return 文でだけ指定できるという仕様も,理屈では可能です。 そうした場合,実用上のメリットとして,たまたま最後に置いた式が意図せず偶発的に戻り値となることを防ぐことができます。 しかし,ブロック自体が結果の値をもつこととバランスがとれませんから,不自然さのそしりは免れません。 これに対し return 文についての実際の Groovy の言語仕様は自然に予想できるものです。 何より Ruby とも共通の仕様です。

このことを利用した階乗関数の例を下記に示す。 ここでは,桁あふれを起こさないように,戻り値の型として 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

4.1.1 クラスの外側の関数

Ruby や Python では日常のことなので違和感が無かったかもしれないが,Groovy の対話シェルで上記の階乗関数 factorial(int n) のようにクラスで囲まずに関数を定義できることに改めて注意しよう。 2.3 節で試したような Groovy スクリプトでも,このように関数を定義できる。

一見すると,こうして定義した「関数」は,クラスとは無関係のように見える。 しかし,一歩実装に踏み込めば,これもやはりクラスに属するメソッドであることが分かる。 さらに言えば this を必要とする普通の非静的メソッドである。 この「関数」の実行時に Groovy が暗黙裏に与える this の値は,対話シェルやスクリプトの実行文脈として Groovy が自動生成する groovy.lang.Script 派生クラスのインスタンスである。 これらの実装詳細を普段から意識する必要はないが,そこから帰結される一つの重要な制限には注意する必要がある。 すなわち,

クラスの外側の関数は,全クラスから利用できる共通ユーティリティをかっちり書くためではなく, クラスを外側から扱うメイン・スクリプトやテスト・スクリプトの共通処理をスクリプト言語らしくさらっと書くためにある。 その限りでは,この制限は制限にならない。 しかるに,もし長期にわたり広範囲に使う共通ユーティリティを書くつもりならば,静的な妥当性検査や実行効率を考慮し,手間をかけて Java のクラスとしてがっちりと書くべきだ。 そのプロトタイプを 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 をメソッドとして扱い,レシーバを陽に与えます。 レシーバには,bar が定義されているのと同じスクリプトの地の部分,つまり,トップレベル (4.3.2 節 参照) での this の値を与えます。 つまり,次のように書き換えます。
class A {
  int foo(x, self) { self.bar() + x }
}

int bar() { 10 }

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

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 を使い,型を指定しないことは,型を意識しないコードであることの表明です。 型決めは処理系に任せた!! というわけです。 もしかしたら将来の最適化では,C# の var や C++11 の auto と同じようにコンパイル時の静的な型推論の対象になるかもしれません。

ただし,少なくとも今の Groovy では,実行時の動的な型に基づいてメソッドを解決しますから,その区別は名目的なものです。 下記の例が示すように Object と宣言されていても,実行時の型に基づいて,例えば String#length() を呼び出すことができますから,Object 型だけでもほとんど間に合います。 結局,今のところ def は,プログラムを書く人にとっては便利な略記法であり,プログラムを読む人にとっては意図を読み取るヒントというわけです。
groovy:000> def s = "abc"; s.length()
===> 3
groovy:000> Object s = "abc"; s.length()
===> 3

より正確に言えば,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
この点で Groovy の def は C++11 の auto と似ています。

構文から宣言であることが明らかならば,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 で定義し,仮引数並びに変数名だけを連ねているプログラムは,実はこのような省略の産物である。

ちなみに前節の 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-1.8.5/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 とは限らない。 そのクラスからメソッドの探索が始まる。 しかし,適合するメソッドがなかなか見つからないとしよう。 最後には共通基底クラス java.lang.Object で探索される。 ようやくこの public static void println(Object self, Object value) が適合する。 その呼び出し時には,仮引数 selfthis が,valuex がそれぞれ引き渡される。

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

Groovy JDK の基礎的な資料としては Groovy JDK API Specification [codehaus.org] があります。 第7章 に挙げた参考文献も参照してください。
C# ファンへ: Groovy JDK とは,Groovy が標準で用意した拡張メソッドのライブラリであると考えてください。

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

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 の振舞は変わらない。 一般に,java.lang.Object のインスタンスをレシーバとし,実際の処理ではレシーバを無視するメソッドは,単に,どこからでも便利に呼び出せる組込み関数として使うことができる。

Ruby ファンへ: 同じような議論を Ruby の Kernel モジュールで定義される「組込み関数」に関して聞いたことがあるはずです。 もし初耳ならば,Ruby チュートリアル - 1. てほどき の「1.4 self と private メソッド」などを読んでみるとよいでしょう。君が知らなかった Ruby の真実の一端がそこにあります。 目立たず地味ですが,これも Groovy が Ruby からインスピレーションを受けた点の一つと言ってよいでしょう。 関係する箇所を下記に引用します。補足してより厳密に言えば,Ruby では,特別な共通基底クラス Object が Kernel モジュールを mix-in しているため,Kernel のメソッドが「組込み関数」としてどこからでも使えるわけです。
したがって,もしも仮にすべてのクラスの基底クラスであるような特別なクラスがあるとすれば,……(略)…… (文脈により self の値は変わるけれども) そのメソッドは Ruby のすべての文脈で呼び出せることになる。

……(略)…… メソッドの動作が,レシーバである self の値に依存していなければ,そのメソッドは,すべての文脈で同じように振舞うことになる。 つまり,そのメソッドは事実上,いわゆる普通のグローバルな関数と同じになる。これが p や puts の正体である。

クラス定義の内側ではない場所,つまり トップレベル (top level) での println の呼出しも例外ではない。 与えられた Groovy スクリプト S.groovy のトップレベルに println の呼出しが書かれているとしよう。 このとき groovy.lang.Script の派生クラスとして,ファイル名にちなんで S クラスが暗黙裏に定義される。 そして S のインスタンスがトップレベルの this として与えられて,println が実行される。 2.3 節 の例では,このようにして暗黙裏に定義された hello クラスのインスタンスが println のレシーバとして与えられる。

以上,println メソッドが何者であり,どうしてそのかたちで使われるのかを説明した。

4.3.2 補足: トップレベル

今や長らく正体不明で使ってきた println が明らかになった。 しかし,君は,クラス定義の外側の「トップレベル」についてあまり釈然としていないはずだ。 スクリプトや対話シェルの地の部分が,このトップレベルにあたる。

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

Java とおなじ基盤に立つ言語なのに,なぜクラスに囲まれない場所で変数やメソッド (関数) を定義できるのか, 定義したとして,Java から見た位置付けはどうなっているのか,今まであまり説明せずに棚上げにしてきた。 できれば説明するほどのことが何もなければよかっただろう。 Groovy によるトップレベルの実現方法は,いくつかのあまり自明でない制限をもたらしている。 しかし,からくりが分かってしまえば,どうということはない。 それを解く鍵は 4.1.1 節で言及し,今また述べた groovy.lang.Script クラスと,暗黙裏に自動生成されるその派生クラスにある。

ここからの説明は,疑問が累積して前に進めない人のためのタネ明かしです。 すぐに役に立つ新しい機能を紹介するわけではありませんから,気にならない人はとばして次の 4.4 節 に進んでください。

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

君がトップレベルでメソッドを定義したとき,Groovy はそのメソッドをこの暗黙裏のクラスの非静的メソッドとして解釈する。 これが 4.1.1 節で説明した「関数」の正体である。

ちなみに,スクリプトのトップレベルの文は,こうして暗黙裏に定義されるクラスの run() メソッドの中にまとめられます。 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 を定義しているファイルであるかのように import して使うこともできます。
groovy:000> import hello
===> [import hello]
groovy:000> h = new hello()
===> hello@ec71f8
groovy:000> h.run()
hello, world
===> null

トップレベルで宣言なしにいきなり代入して変数を定義したとき,それは暗黙裏のクラスの非静的 public フィールドとして,事実上,解釈される。 したがって,トップレベルの関数から自由にアクセスできる。 普通のクラスのメソッドからは (素直には) アクセスできない。

それに対し,トップレベルで (型の指定や def を使って) 宣言した変数は,暗黙裏のクラスに自動的に作られる run() メソッドのローカル変数として解釈される。 したがって,同じトップレベルの文 (つまり暗黙裏のクラスの run() メソッドの本体) からはアクセスできるが, トップレベルの関数 (つまり暗黙裏のクラスの run() 以外のメソッド) からはアクセスできない。 もちろん,普通のクラスのメソッドからもアクセスできない。

Ruby ファンへの弁明:
4.1.1 節 で説明したように,トップレベルで定義した関数はそのままではクラスのメソッドの中から呼び出せません。 つまり,Ruby と異なり,Groovy では,グローバルな関数をユーザが自明な方法で定義することができません。 スクリプトの実際の取り扱いについて説明した今,ようやくこの制限について弁明することができます。

Ruby では,トップレベルで定義した関数は,共通基底クラス Object のインスタンス・メソッド (= 非静的メソッド) として定義されます。 Object クラスそのものが,その場その場でダイナミックに定義されていくわけです。 しかるに Groovy では,Object はあくまで定義済みの Object クラスのままであり,トップレベルの関数は,そのスクリプトの暗黙のクラスの非静的メソッドとして定義されるだけです。 Java との互換性のため,スクリプトにおいて明示的に定義される普通のクラスは,この暗黙のクラスと継承関係などの特別な関係をもちません。 したがって,そこから見れば「関数」は単なる無関係なクラスのメソッドにすぎませんから,明示的なレシーバ無しにはアクセスできないわけです。

もちろん,Groovy の処理系が手間となにがしかの犠牲をいとわなければ,トップレベルの関数を Ruby の関数や Groovy JDK の println などと同じようにグローバルにすることは決して不可能ではなかったでしょう。 例えば,暗黙のスクリプト・クラスをダイナミックに Groovy JDK のメソッド探索メカニズムに組み入れればよいわけです。 しかし,少なくとも今の Groovy はそうしていません。 オブジェクト指向の枠から外れすぎた関数の乱用を防いでいる一面もあります。

トップレベルに対して暗黙裏にクラスが定義され,そのインスタンスが this になるという事実は, this そのものを表示するスクリプトを書いて実験的に確認できる。 下記では .getClass().getSuperclass() を使って this のクラスと基底クラスを調べている。 これはそれぞれ .class.superclass と書いてもよい。

println this

c = this.getClass()
println c
println c.getSuperclass()
$ groovy test1.groovy
test1@1f01a29
class test1
class groovy.lang.Script

対話シェルのトップレベルも同様にして調べることができる。

groovy:000> this
===> groovysh_evaluate@1a9883d
groovy:000> c = this.getClass()
===> class groovysh_evaluate
groovy:000> c.getSuperclass()
===> class groovy.lang.Script

4.3.3 注意: 対話シェルは1行ごとに別々の顔

ここで対話シェルには注意すべき一つの点がある。

対話シェルでは1行ごとに this の値が異なる。 つまり,その Script インスタンスは1行ごとに新しく作られている。

groovy:000> this
===> groovysh_evaluate@c42091
groovy:000> this
===> groovysh_evaluate@56adde

対話シェルのトップレベルで定義したメソッドと,宣言なしに代入して定義した変数は,新しく作られる Script インスタンスに引き継がれる。 しかし,型や def を使って宣言した変数は引き継がれず,その1行限りになる。

1行1行が別の run() メソッドとして実行され,宣言した変数は,そのローカル変数としてその都度,消えて行くわけです。

したがって,4.1 節 で使った単体のブロックは,実は,対話シェルでは必要ない。 ブロックで囲まなくても,宣言した変数はその1行限りで消える。

groovy:000> def 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)
        ...

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
}

と書いたとき,実際には下記のように書いたことと等価である。

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 にも及び,現在さらに幅広く普及しようとしている。

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

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

オブジェクト指向の元祖のように言われている 1980 年ごろの Smalltalk-80 は,当時すでにブロック (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 [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 プロパティは主に,よそのクロージャを取り入れて自分のメソッドのように使うためにあります。 ある意味,かたくななクロージャに,郷に入っては郷に従うダイナミックさを導入します。 もしも Groovy に最適化コンパイラができた暁には,静的スコープでコンパイル時に解決できる (ほとんどの) メソッドには,Java と同じ通常のメソッド呼出しが使われ,解決できなかった (一部の) メソッドには delegate をレシーバとしたリフレクション API 経由のダイナミックなメソッド呼出しが使われることになるはずです。

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 })

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 にはない。 Groovy は次のようにコレクション用の Groovy JDK メソッドを提供している。

つまり,Groovy の java.util.Collection を Ruby の Enumerable モジュールとほぼ同一視することもできますが,each や collect などのメソッドは Collection に限らず,Groovy のあらゆるオブジェクトで使うことができる,というわけです。

コレクション用のメソッドに対して,整数などのオブジェクトは,要素数1のコレクション (つまり,数学用語としての singleton, 単集合) として振舞う。 その一方,おそらく君が予想しているとおり,文字列や生の配列は,添字演算子で取得できるような要素,つまり長さ 1 の文字列や各配列要素を順にクロージャ引数に与える。

groovy:000> 1234.each { println it + 56 }
1290
===> 1234
groovy:000> "1234".each { println it + 56 }
156
256
356
456
===> 1234

6. おわりに

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

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

7. 参考文献

  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 節)。


Groovy に関しては 2009年の夏ごろから NetBeans はあまり変わっていない (当時の Windows 上の NetBeans 6.7)。 現行の NetBeans 7.1 に同梱されている Groovy 1.6.4 は 2009年の 7月から 8月の変わり目にリリースされた。 2.2 節 でインストールした新しい Groovy 1.8.5 をこのサンプル・プロジェクトで使うには,例えば次のようにすればよい。

  1. 「プロジェクト」タブの「ライブラリ」を右クリックする。 「JAR/フォルダを追加」で,Groovy 1.8.5 のフォルダの embeddable にある groovy-all-1.8.5.jar を選択する (君はこの JAR ファイルを 2.4 節 で使った)。 「プロジェクト」 タブの「ライブラリ」に groovy-all-1.8.5.jar が追加される。
  2. 「プロジェクト」タブの「ライブラリ」から Groovy 1.6.4 - groovy-all.jar を削除する。
  3. もし「ファイル」タブに dist が生成されており,その lib の下に groovy-all.jar が残存しているならば,「実行」メニューの「主プロジェクトを削除して構築」を実行する。 残存している groovy-all.jar が自動的に削除される。
Eclipse [eclipse.org] については Groovy 本家ページ [codehaus.org] からおおむね最新バージョンに追従してプラグインがリリースされています。 参考文献 3.「プログラミング Groovy」に付録としてその丁寧な解説があります。


目次へ


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