Groovy の最新版 (1.6, 1.7, 1.8) ではパフォーマンスと作業性の向上に加えて,さまざまな機能の追加が行われている。InfoQ では先日,Groovy プロジェクトのリーダである Guillaume Laforge 氏から詳しい情報を得ることができた。
最初に話題となったのは Groovy 言語 の改善の歴史についてである。Java シンタックスをベースとして構築されたこのプログラム言語は,Pyton や Ruby,Smalltalk などの言語機能に加えて,独自のトリックも数多く備えている。さらに話は,今後の Groovy の開発予定についても及んだ。ドメイン固有言語としての Groovy の改良,整数値処理のパフォーマンス向上,AST 変換をトリガとするアノテーション,GPars 並列/並行ライブラリ,JSON をサポートする新たな組込言語,Grape パッケージ管理の改良,アノテーションへのクロージャ転送,スタックオーバーフローの不安なく再帰的クロージャ呼出をサポートするクロージャ処理改良など,Guillaume 氏は多くの話題について説明してくれた。これらはすべて Groovy 1.8 で提供される機能だ。
AST 変換は,他の言語では避けられないボイラープレートコードなどの生産性低下要因を取り除いて, Groovy のコード記述を合理的にする。Grape は,Maven/Ivy タイプのパッケージ依存性管理を Groovy のスクリプトレベルで提供するもので,スクリプトのスタンドアロンでの実行と依存リソースの取得が可能になる。さらにアノテーションにクロージャを渡せることにより,契約による設計スタイルの開発が Groovy で実現される。
Groovy の普及状況や 1.9 および 2.0 のロードマップの内容,さらには計画中の multi-catch や invoke dynamic など Java 7 機能のサポートなども話題に上がった。invoke dynamic のサポートは,Groovy の全体的なパフォーマンスに関するひとつの節目になるかも知れない。氏は Groovy の普及率や採用状況についても言及した。Groovy では以前から,ドキュメントを目的とする型定義が可能であった。しかし Groovy は動的言語でありながら,Dart のように静的なコンパイル時チェックも手段として取り入れるつもりのようだ。Guillanume 氏はこの件についても説明してくれた。
InfoQ: Groovy 1.8 に関する最新情報を教えてください。
Groovy 1.8 で私たちは,いくつかの新しいことに取り組みました。
最初にあげたいのは,Groovy が,表現力と可読性の高いビジネスルールを記述するためのドメイン固有言語 (Domain-Specific Languages) を定義する能力の高さで知られている,という点です。その目的のために私たちは,文法拡張 ("コマンドチェーン" と呼んでいます) を導入しました。これはチェーンされたメソッドコールのドットやカッコを省略可能にすることで,見栄えのよい DSL を定義できるようにするものです。このアプローチなら,DSL で通常の英語の文章を記述することさえ可能なのです!
ランタイムパフォーマンスの向上にも継続して取り組んでいます。中でも基本データ型の計算の領域には力を注いでいます。数値処理にモンキーパッチを行わないことが前提になりますが,変数を基本データ型として参照している場合,Groovy は JVM バイトコード命令を使ってそれを処理します。2つの整数の加算であれば,boxing / unboxing を持ち込んで Integer などのメソッドコールをするのではなく iadd 命令を使用する,という具合です。これによって基本データ型計算のパフォーマンスが,従来の Groovy の各バージョンに比べてかなり Java に近くなりました。
その他には,GPars 並列処理ライブラリが Groovy のディストリビューションに追加されています。Groovy クロージャの機能を拡張するため,さらにいくつかのユーティリティを追加する作業を行っているところです。JSON ペイロードを扱うためのサポートも追加しました。さらに,使い勝手のよい新たな AST (Abstract Syntax Tree, 抽象構文木) 変換や,アプリケーションに統合可能な Groovy スニペットをカスタマイズする手頃なコントロールトリック (無限ループ回避のような) もいくつか追加しています。
これらすべての機能に関する詳細については,ぜひ Groovy 1.8 リリースノート で確認してください。
InfoQ: AST 変換とは何でしょう。開発者はそれをどうやって利用するのでしょうか。
AST 変換とは,抽象構文木 (Abstract Syntax Tree) 変換という意味です。言うならば,コンパイラプラグインのようなものです。AST というのはプログラムの内部表現形式で,Java バイトコードにコンパイルされる前のクラスやメソッド,プロパティ,文,変数ノードなどで構成されます。AST 変換は Groovy のコンパイル処理にフックするための機能です。最終的なクラスのバイトコードを生成する前に,プログラムの構造を修正することが可能になります。この機能では新たなメソッドを追加したり,コードに透過的なチェックを追加したり,その他のクラスを生成したりすることができます。他にもいろいろ面白い利用法があると思います。
このような変換機能が Groovy には2種類あります。ローカル変換とグローバル変換です。グローバル変換の方はすべてのコンパイルユニットに適用されるもので,ローカルの方はアノテーションで起動されるものです。後者では,例えばクラスに @ToString をアノテートして,クラスに気の利いた toString( ) 実装を追加するような変換を実行することができます。
動的言語で一般的に用いられるランタイム時のメタプログラミングの代わりに,この種の変換を使用したコンパイルタイム・メタプログラミングを行うことによって,ユーザの作業がこれまでよりシンプルになります。さらにコンパイル時の機能であることには,ランタイムで余分なオーバーヘッドを発生しないという,優れたメリットもあります。全体的に見れば,ユーザのコードをこれまで以上に簡潔で表現力豊かなものにしてくれるもの,と言っていいでしょう。
InfoQ: 新たに追加された AST 変換の例を挙げていただけますか。
Groovy 1.6 と 1.7 では便利な変換機能がいくつか導入されました。アノテートされたフィールドのひとつにメソッドを委譲(delegate) する @Delegate や,名前のとおりクラスを不変(immutable) にする @Immutable などです。
Groovy 1.8 ではさらに新しい変換が加わっています。クラスにロガーを挿入して,ログレベルをチェックする "if" でログ文をラップする @Log 変換のバリエーション,スーパーコンストラクタに処理を委譲する形式のコンストラクタを追加する @inheritConstructor (Exception クラスの拡張が必要な場合を想像してみてください),java.util.concurrent の ReentrantReadWriteLock の利用を簡単にする @WithReadLock / @WithWriteLock などといったものです。
@ToString, @EqualsAndHashCode, @TupleConstructor, それから @Canonical を組み合わせれば,toString( ), equals( ), hashCode( ) メソッドと,パラメータ宣言したさまざまなプロパティを備えたコンストラクタが実装できます。
いつでも同じですが,これらすべての新しい変換の目的も,他の言語では避けられないような決まりきったボイラープレートコードの強要から開発者を解放して,彼らが本当にやらなければならないビジネスコードの記述に集中できるようにすることで,作成されるコードを合理化する点にあるのです。言語の生産性,それこそがプロジェクト成功の鍵なのです!
InfoQ: 自分自身で AST 変換を記述するのは,どの程度大変なことなのでしょう。
AST 変換を記述するには Groovy AST API に関する多少の知識が必要になります。ですからそれほど簡単とは言えません。しかしありがたいことに Groovy のバッグには,このような変換の記述を助けてくれるトリックがいくつも入っているのです。まず最初に,Groovy コンソールで Groovy AST の構造を見ることができます。その内容を真似ながらコードを作成したり,あるいは修正すればよいのです。AST Builer クラスというのもあって,AST を構築するために3つの異なるメカニズムを提供しています。コードフラグメントから構築する方法とクロージャ内のコードから構築する方法,もうひとつは AST クラスを直接操作するよりも冗長性の少ないやり方で,AST 構造を自分自身で構築するビルダ API を使用する方法です。AST 変換を書くのは結構大変な作業ですが,Groovy がこのタスクを手助けしてくれるのです。
InfoQ: GPars とは何でしょう。誰が使うもので,なぜ Groovy 1.8 で導入されたのでしょうか。
Groovy チームは数年前から,(マルチプロセッサやマルチコアによる) 並列性向上というコンピュータのトレンドに関心を持っていました。Groovy にこのトレンドのサポートが必要なことは,その頃からすでに明らかでした。並列処理と並行性にまつわる問題に取り組むためには,より高度なレベルのパワーと表現力を提供する必要があります。Java と JDK ではスレッドなどのプリミティブな要素から wait/notify/notifyAll といったメソッド,さらに最近では java.util.concurrent パッケージの優れた拡張など,さまざまなものが提供されています。
確かに Groovy も,これらの面でコア部分にいくつか改良を加えてはいます。しかし私たちがユーザに提供したいと思っている,もっと高いレベルのコンセプトが存在するのです。GPars プロジェクトはこのような理由で始まりました。リーダは JetBrain の提唱者である Vaclav Pech 氏です。このプロジェクトで私たちは,アクタ(Actor) や データフロー並列性(Dataflow Concurrency), ソフトウェア・トランザクションメモリ(Software Transactional Memory), エージェント(Agent) といった,より高レベルの機能を提供したいと考えています。
GPars はプロジェクトとして,独自のロードマップを持っています。当初私たちは,このプロジェクトを Groovy 内に直接取り込もうと考えていました。しかしプロジェクト周辺に形成されたコミュニティの存在と,プロジェクト自体が独自の領域で発展するために,Groovy ディストリビューションに組み込む形式を選択したのです。
もうひとつ GPars の好ましい点を上げるとすれば,それが単なる Groovy の並列/並行ライブラリではなく,Java API のサポートによって Java からも利用可能であること,言語として Groovy の利用を強制していないことです -- ただし当然のことながら,Java では Groovy ほどコンパクトなコードを記述できません。
次の並列処理あるいは並行処理のニーズには Gpars プロジェクト をぜひ検討してみてください。特に 詳細に記述された GPars ユーザガイド には一見の価値があります。
InfoQ: Grape とはどのようなもので,今回どのような改良が行われたのでしょう。
Grape は @Grab アノテーションと合わせて,スクリプトあるいはクラスが必要とする依存性を定義するためのものです。例えばスクリプトに @Grab("groupId:artifact:version") と追加してインポートの定義をしておけば,Groovy がそのスクリプトの実行時にファイルのダウンロードとキャッシュを実行して,classpath で参照可能なようにしてくれます。スクリプトを別の作業者と共有するような場合には非常に便利です。依存ファイルをパッケージする必要がなく,スクリプトだけを共有できればよいからです。
Groovy 1.8 では @Grab と @GrabResolver アノテーション (リポジトリを特定する) に渡す文字列に関する拡張が行われました。依存性やリポジトリの場所の記述がコンパクトになっています。
InfoQ: JSON の読み込みと生成に関しては,どのようなサポートが追加されていますか。
今や JSON はそこら中で見られますし,Web には JSON ペイロードのみをサポートする REST API もたくさんあります。Groovy は以前から,XML ビルダとパーザによる XML の解析と生成の両面で XML の操作を得意としていました。Groovy 1.8 では JSON にも,それと同じレベルのサポートを追加したいと考えたのです。そのために,JSON コンテントをパースする "slurper" と,ストリーム/非ストリームの2つの方法で JSON ペイロードをビルドできる "builder" を導入しました。どちらも非常に簡潔な方法で,特別なマーシャリングを実装しなくても使用できます。
例えば person という JSON オブジェクトの生成は,次のように記述できます。
def json = new JsonBuilder() json.person { name "Guillaume Laforge" age 34 }
これは次のようなオブジェクトを生成します。
{ "person": { "name": "Guillaume Laforge", "age": 34 } }
あるいは GitHub にある Groovy リポジトリミラーのコミットメッセージを,以下のようなコード記述で解析できます。
import groovy.json.* def endpoint = "https://api.github.com/repos/groovy/groovy-core/commits" def content = endpoint.toURL().text def parser = new JsonSlurper() def commits = parser.parseText(content) // navigate the object graph // and print each commit message commits.commit.message.each { println it }
JSON サポートは既存の XML サポートによく似ていて,JSON ドキュメントの処理をはるかに簡単にしてくれます。
InfoQ: クロージャの改良について,特にその機能的な特長に関する話題がいくつが出ましたが,それらについて説明して頂けますか。
Groovy の一押しの機能は closure ですが,JDK のさまざまなコレクションクラスを拡張する Groovy Development Kit のメソッド (each / collect / findAll / その他) もそれと並ぶほど重要な基本機能です。これらを使えば,コレクション値の走査やフィルタ,あるいはコレクションの変換など,数多くの操作を行うことができます。
Groovy 1.8 ではクロージャに trampoline() メソッドが追加されました。これは再帰的なアルゴリズムでの再帰コールによるスタックの暴発を回避して,クロージャ呼出の連続的なスタックアップを可能にするものです。階乗計算を行うようなクロージャ関数は 40 程度までならば結果を計算できますが,それを越えると StackOverflowException を起こしてしまいます。しかし trampoline( ) を使って少し修正を加えた実装では,暴発を起こすことなく factorial(1000) あるいはそれ以上の計算をクロージャで行うことができるのです。
クロージャの合成と逆合成が,それぞれ左シフト << と 右シフト >> で可能になりました。これによってクロージャの計算を合成した新たなクロージャをコールしたり渡したりすることが可能になります。
また,与えられたパラメータセットに対する前回の呼び出し結果を記録する memorize( ) 関数が追加されました。長時間を要する計算には特に便利ですが,注意が必要なのは,対象とするクロージャはサイドエフェクトを持たず,同じパラメータに対して常に同じ結果を返すものでなければならない点です。そうでなければ,実行結果を見ていきなり驚くことになります! このメモ機能は LRU キャッシュを使用しますが,キャッシュする処理結果数を指定してキャッシュをより詳細にコントロールできるバリエーションもあります。
InfoQ: アノテーションのパラメータとしてクロージャが指定できるようになっていますが,アノテーションにクロージャを渡すことが必要な理由は何でしょう。クロージャを取得するアノテーションとはどのようなものでしょうか。
Java のアノテーションには,受け付け可能な値がプリミティブと文字列,アノテーション,クラス,およびそれらの配列に限られるという,ある種の制限があります。自分自身で定義したクラスのインスタンスなどを渡す,などといったことはできません。Groovy はクロージャをクラス形式にエンコードする方法で,アノテーションの制限を越えることに成功しています -- ですから Java でも,パラメータとして Class を渡すことができれば十分なのです。アノテーションへのクロージャパラメータを使えば,フィールドやメソッドを @Validate({ name.size() > 0 )} のようにアノテートすることで,メソッドパラメータやフィールド値に検証ルールを追加することができます。フレームワーク開発者にとっても便利でしょう。
GContracts ライブラリでは,すでにこの機能が利用されています。このライブラリは,事前条件や事後条件,不変条件などをアノテーションのクロージャパラメータによって実装する方法で,Groovy に "契約による設計 (Design by Contract)” を追加するものです。ですから,この機能で何ができるかを見てみたいというのでしたら,GContracts を参照 するとよいでしょう。
InfoQ: Groovy のプログラミングには,どの IDE を使用していますか。
Eclipse SpringSource Tool Suite と IntelliJ IDEA です。
IntelliJ IDEA で8年ほど開発を行っていますから,かなり使い込んでいることになります。Eclipse はそれよりも長期間使用しています。ただし実際にはどちらの IDE も,通常期待するようなすべての機能 (コードのブラウジング,ナビゲーション,ホーバ,リフレクション,デバッグ,実行など) に関して,Groovy と Grail をすばらしくサポートしています。Groovy と Java で開発をするのであれば,どちらも非常によい選択です。
NetBeans も旧来の Groovy サポートの更新作業中だと聞いていますので,これら2つの競争相手として,間もなく競技の場に戻ってくるだろうと思います。
InfoQ: Groovy の普及状況についてはどうでしょうか。利用率の向上やコミュニティの拡大はありますか。
言語やライブラリに関するオープンソースプロジェクトでは,普及率の追跡はそれほど簡単ではありません。メーリングリストの購読者数やダウンロード状況,さらにはオンラインやカンファレンスで次に試してみたい言語を投票するなどといった主観的なものも含めて,さまざまな指標を確認する必要があります。これらのいくつかの項目で Groovy は非常に好調です。現時点では JVM 上の次世代言語として,もっとも人気が高いと言っていいでしょう。
例えば前回リリースのダウンロード状況を見ると,最初の2ヶ月間で40万件近い Groovy ディストリビューションのダウンロードを Codehaus (Groovy プロジェクトをホストしている法人) で達成しています。しかもこの数には Grails や Gradle,Griffon などといった,Groovy を利用している他のプロジェクトによる Maven Central からのダウンロードは含まれていないのです! ですから最大限控えめに見積もったとしても,少なくとも 50万人の Groovy ユーザがいることになります! あるいはその2倍以上かも知れませんが,いずれにしても正確な数値を言うのは難しいですね。
InfoQ: 次のメジャーバージョンの状況について教えてください。何が提供される予定でしょうか。
私たちは先日ロードマップを更新して,バージョン番号のスキームを少し変更しました。次のバージョンは当初計画の 1.9 ではなく,Groovy 2.0 になる予定です。興味深い機能がいくつか追加されていて,フルバージョンアップに相応しいものだと思うからです。それ以外にもいくつか,エキサイティングな機能を準備していますので,予定よりも少し早めにバージョンが上がることになるかも知れません。ただし Google Chrome や Mozilla Firefox のようにバージョン番号を上げていくつもりはありません。これまでの私たちのペースで,1年程度でメジャーバージョンをリリースしていく予定です。Groovy 2.0 のリリースは,2012 年の第1四半期の終わり頃を予定しています。
Groovy 2.0 に関する開発作業の内容ですか? そうですね,主な機能は3つあります。invoke dynamic や Project Coin 拡張など JDK 7 のサポート,静的型チェック,それから現在検討中の静的コンパイルに関するものです。
まず最初に,JDK7 にアップグレードして invoke dynamic バイトコード命令と API が使えるようになれば,Groovy の動的処理の全面的なパフォーマンス向上が実現できます。現時点では正確な向上率が分かりませんが,このサポートによって Groovy ランタイムのパフォーマンス向上が継続されることは間違いありません。最終目標として,JDK 7 以降をベースとする Groovy の将来のバージョンでは,これまでの Groovy を高速化する過程で積み上げてきた最適化を取り除いていきたいと思っています。JDK クラス自体と JTI による最適化に依存することで,Groovy を今よりスリムにしたいのです。JDK 5 または 6 を引き続いて利用するユーザに対しては,invoke dynamic をバックポートするか,または旧バージョンの JDK のサポートを継続する別系統のコードベースへの移行を検討しています。新しいバージョンの Groovy を使用するための要件として,JDK 7 へのアップグレードが必須にならないようにしたいと思います。
Java 7 の Project Coin 拡張ではマルチキャッチやバイナリリテラル,数値リテラル中のアンダースコアなどが利用可能になります -- その他については,例えば文字列 switch などは Groovy の switch の方が機能的に上なので,実装する価値はありません。ところで,ここで面白いのは,これらの言語拡張が旧版の JDK でも利用可能になることです。ですから,例えばマルチキャッチを利用するために JDK 7 にアップグレードする,というような必要はありません。
次は静的な型チェックについてです。以前から気付いてはいたのですが,Groovy には,Java のスクリプティング言語としてアプリケーションに組み込んで,Java のスーパーセットに近いものとして利用しているユーザがかなりの割合で存在しています。そのようなユーザは,Groovy の動的言語としての機能をフルには利用しないで,Java と同じようなコーディング方法で内部 API 操作などの処理を実装しているようです。この手のユーザベースに共通しそうな不満は,メソッド名や変数にタイプミスがあった場合に,Groovy がそれをコンパイル時にはエラーとしてチェックせず,実行時に初めてエラーとなる点です。そこで Groovy 2.0 では @TypeChecked アノテーションを導入して,静的チェックを実行するメソッドやクラスをアノテートできるようにする予定です。巧妙な型推論やメソッド名や変数名のチェック,代入の正当性検証などの処理が実行されることになります。
静的型チェックモードの機能を備えた Groovy コンパイラは,これまでよりずっと多くのコード情報を得られるようになります。他にも興味深い利用方法があることでしょう。高度なメタプログラミングのトリックを使用せず,かつ正しく型チェックが行われているコードベース部分については,私たちは Groovy のメタオブジェクトプロトコルをバイパスして,Java と同じ種類のバイトコードを生成する,というスタンスを選択しています。Groovy を動的にしているのは MOP です。しかし Groovy の動的な特徴はまったく必要ではないが,Java と同程度のパフォーマンスを必要とする部分については,静的コンパイルも利用できるのです! 私たちが Groovy 2.0 で検討しているのは,そのようなものです。Groovy コミュニティ内では現在,静的型チェックと静的コンパイルという2つの側面に関する議論が活発に行われています。意見をお持ちならば,ぜひとも議論に参加してください。