Rubyist のための他言語探訪 【第 5 回】 Groovy

著者:みずしま

はじめに

「Rubyist のための他言語探訪」は、Ruby 作者まつもとゆきひろが Ruby と関係があったりなかったりする他の言語を紹介していく連載ですが、今回は特別編としてみずしまによる Groovy の紹介をお届けします。

今回は、Groovy という言語を紹介します。

Groovy とは

Groovy は、James Strachan らによって 2003 年に開発が開始されたプログラミング言語です。処理系は、Java VM (Java Virtual Machine) 上で動作するため、Java VM (J2SE1.4 以上) が動作する環境では、基本的にどこでも動作します。Groovy は、ファイルや文字列処理など、Java で書くと冗長になりがちな処理を簡潔に記述するためのスクリプト言語として開発されました。Groovy を使うと、Java Platform 上でこれらの処理を、Ruby, Perl, Python などのスクリプト言語のように簡潔に記述することができます。

Groovy の特徴

開発者の James Strachan が何度か公言していますが、Groovy は Ruby に非常に大きな影響を受けた言語であり、特に文法面で Ruby の影響と思われる部分があちこちにあります。 ここでは、主に文法面での Ruby との比較を通して、Groovy の特徴について見ていくことにしましょう。

基本的な文法

Groovy の文法は Java 言語をベースにしており、Java 言語にある機能 (クラス定義など) は、Java とほとんど同じ形で Groovy でも使用することができます。また、それに加えて、リスト (Ruby の配列に相当) やハッシュのリテラルなど、Ruby や Python 由来の機能も使用することができます。例えば、リストやハッシュを表現するには、次のように記述します。ハッシュはあまり似ていませんが、リストの記法は Ruby の配列に非常によく似ていることがわかると思います。なお、下のコードで使われている // は行コメント (Ruby の # に相当) であり、Java のそれと同じです。

[1, 2, 3, 4, 5]  // Ruby の [1, 2, 3, 4, 5] とほぼ同じ
["x" : 1, "y" : 2]  // Ruby の {"x" => 1, "y" => 2} とほぼ同じ

ローカル変数の宣言は def というキーワードを使って、次のように記述します。このとき、変数 x の型を宣言する必要はありません。Groovy は動的型の言語であり、Ruby と同様に、変数にはどんな型の値でも代入することができるのです。

def x = [1, 2, 3, 4, 5]  // x に [1, 2, 3, 4, 5] のリストを代入

Groovy でメソッドを定義するには、def というキーワードを使って、次のように記述します。ブロックをくくるために中括弧を使う所は Java に似ていますが、def というキーワードを使うのは Ruby っぽいかもしれません。

/*
 * 階乗を計算するメソッドを定義
 */
def fact(n) {
  if (n < 2) {
    return 1
  } else {
    return n * fact(n - 1)
  }
}

定義したメソッドは次のようにして呼び出すことができます。このとき、Rubyと同じように、引数を囲む括弧を省略することもできます。

println(fact(3))  // 6 を出力
println(fact 3)   // 6 を出力。 fact の呼び出しの括弧を省略

Ruby のように文字列の中に式を埋め込むこともできます。Groovy で文字列に式を埋め込むには、${} を使います。例えば、次のコードを実行すると、"1 + 1 = 2" と出力されます。

println "1 + 1 = ${1 + 1}"

クロージャ

Groovy にはファーストクラスのクロージャがあります。Groovy のクロージャの文法は、開始と終了に {} を使うことや、呼び出しに call という名前のメソッドを使うなど、Ruby のクロージャ (Proc オブジェクト) の文法と類似していますが、いくつかの相違点があります。

まず 1 つ目は、引数が 1 つだけのクロージャを表現するときに、引数を宣言しなくても、it という名前によってクロージャの引数を参照できるという点です。例えば、引数を 2 乗するクロージャは次のように記述することができます。この機能は、特に短いクロージャをメソッドに渡したい場合に効果的です。

def power2 = {it * it}  // 引数を 2 乗するクロージャ

また、クロージャの引数リストの宣言の区切りに、-> という記号を使うという点も挙げられます。実は、以前のバージョンの Groovy では、Ruby と同様に、| を区切りとして使うことができたのですが、| がビット単位の or 演算子として既に使われており、それとの区別が難しいということで、バージョンアップの際に -> を使うように変更されました。

// 2 つの引数を加算するクロージャ。Ruby の add = proc{|x, y| x + y} に相当
def add = {x, y -> x + y}
// 2 つの引数を減算するクロージャ。Ruby の sub = proc{|x, y| x - y} に相当
def sub = {x, y -> x - y}
// 2 つの引数を乗算するクロージャ。Ruby の mul = proc{|x, y| x * y} に相当
def mul = {x, y -> x * y}
// 2 つの引数を乗算するクロージャ。Ruby の div = proc{|x, y| x / y} に相当
def div = {x, y -> x / y}

最後に、1 番重要な相違点として、Groovy のクロージャはメソッド呼び出しの最後の引数になるときに限って、文法上特別扱いされるという点が挙げられます。例えば、クロージャを引数に取って、そのクロージャを呼び出すメソッドは、次のように定義及び使用します。Ruby では、メソッドに渡すブロックを単なるクロージャではない特別な要素として扱っていますが、Groovy ではこのように文法を工夫することで、Ruby のブロック付きメソッドのような簡潔な記述を可能にしています。

def call(arg, yield) {
  yield.call(arg)
}

print(call(2){x -> x * x})  // 4 を出力

Groovy Markup

Groovy には標準ライブラリとして、Groovy Markup と呼ばれる、Groovy のメソッド呼び出しの構文だけで簡単にツリー構造を生成可能なライブラリがあります。現在のところ、GUI コンポーネントを生成するものや、XML の DOM (Document Object Model) を生成するものなどがあります。また、Groovy のユーザが自分用の Groovy Markupを定義することもできます。

例えば、次のプログラムは XML の DOM を生成するための Groovy Markup を使ったプログラムですが、これを実行すると、

 import javax.xml.*
 import javax.xml.parsers.*
 import groovy.xml.*

 def builder = new DOMBuilder(
   DocumentBuilderFactory.newInstance().newDocumentBuilder())

 def document = builder.html {
   head {
     title("他言語探訪問番外編:Groovy")
   }
   body {
     h1("はじめに")
   }
 }

次のような XML を表現する DOM オブジェクトが生成されます。

 <html>
   <head>
     <title>他言語探訪番外編:Groovy</title>
   </head>
   <body>
     <h1>はじめに</h1>
   </body>
 </html>

なお、Groovy Markup は、Groovy の構文ではなく Groovy の機能を使って作られたライブラリであり、Groovy 以外でも Groovy と似た機能を持った言語なら作ることができます。*1

Groovy によって Java の クラスライブラリに追加されたメソッド

Groovy では、リフレクションの機能を使って、Java のクラスライブラリに擬似的に多数の便利なメソッドが追加されており、Groovy からはそれらのメソッドを Java のクラスのメソッドと同様に扱うことができます。これらのメソッドには、Ruby に影響を受けたと思われるものが多く含まれています。

例えば、Java でリスト構造を表現するのに使われる List インタフェースには each, collect, inject などのメソッドが追加されています。これらのメソッドは次のようにして使うことができます。

def list = [1,2,3,4,5]
list.each{e -> println e}  // 1 から 5 までを改行をはさんで出力
println(list.collect{e -> e * e})  // [1,4,9,16,25] を出力
println([1,2,3,4,5].inject(0){x,y -> x + y})  // 15 を出力

例を見ればわかる通り、Ruby の each, collect, inject などのメソッドに名前も使い方も非常によく似ています。これらの例からも、Groovy が Ruby に大きな影響を受けていることがわかると思います。

Groovyの現状と今後

Groovy は JSR (Java Specification Request)*2-241 として JCP (Java Community Process)*3 によって標準化作業が進められています。

また、一時期は Java の次期バージョンで標準添付されるのではという噂もありましたが、現在のところ、Java の次期バージョンには Groovy ではなく Java で書かれた JavaScript エンジンである Rhino が添付されることになっており、Groovy が Java に標準添付になることは当分は無さそうです。

まとめ

Groovy について簡単に紹介してみましたが、いかがでしたでしょうか。 Groovy は、Java をベースにしながら、Ruby などから多くの便利な機能を取り入れることで、実用的な言語に仕上がっていると思います。

ただ、個人的に気になるのは、標準化作業が始まってから、文法面で冗長になった部分が ある点です。例えば、ローカル変数の定義には def が必要ですが、これは標準化が始まってから改訂された部分です。それ以前の Groovy では、Ruby のように def 無しでローカル変数を定義することができました。このような変更が行われた背景には、様々な議論があったのかもしれませんが、簡潔な記述を重視するべきスクリプト言語としては、あまり好ましくないように感じます。

また、文法が Ruby に比べていまいちこなれていないのも問題です。例えば、以下の Ruby プログラム

puts [1, 2, 3, 4, 5].inject(0){|x, y| x + y}

に相当する Groovy のプログラム

println [1, 2, 3, 4, 5].inject(0){x, y -> x + y}

は、

(println [1, 2, 3, 4, 5]).inject(0){x, y -> x + y}

のように解釈されてしまい、Ruby のような挙動にはなりません。

今後、このような文法面での使い勝手の悪さが改善されれば、より多くの人にとって使いやすい言語になるのではないかと思います。

参考文献

Groovy - Home
Groovy の公式ページ。Groovy の概要、チュートリアル、マニュアルなどがあります。処理系及び開発環境もダウンロードできます。全て英語です。
Groovy - Wikipedia
Wikipedia 日本語版の Groovy の解説。Groovy の歴史、特徴などについて簡単に解説されています。
標準化進む新スクリプト言語 "Groovy"
@IT の Groovy の紹介記事。Groovy の特徴や用途などについて、簡単に触れられています。

著者について

みずしまは、プログラミング言語 (特に静的型言語) 好きな大学生です。現在、Onion というプログラミング言語を作って公開しています。将来は、まつもとゆきひろさんのような言語設計者になるのが夢。


*1 実際にGroovy Markup に影響を受けて Ruby 版 Groovy Markup を作成された方もいるようです。 (参考 URL: http://onestepback.org/cgi-bin/originalrublog.cgi/Tech/Ruby/BuilderObjects.rdoc )

*2 JSR: Java 関係の技術において、新規の仕様や既存の仕様に対する改訂を提案する文書。

*3 JCP: Java 関係の技術の標準化を進める機関。多数の企業・個人で構成されている。