プログラミングGROOVY別冊:第8章 Groovy 2.0の新機能

Created User: Kazuchika Sekiya / Date Created:2012/09/25 15:06:00 / Date Created:2012/12/25 23:08:15 / Language:ja_JP

8.1 静的型チェック

Groovy 2.0では新機能として「静的Groovy」が導入されました。静的Groovyは、「静的型チェック」と「静的コンパイル」の2つの機能から実現されています。本節では静的型チェックについて説明します。

静的型チェックとは

Groovy 2.0の静的型チェック機能は、動的型言語であるGroovyに、静的な型チェック、つまりコンパイル時の型チェック機能を新たに導入するものです。

もともとGroovyでは、

int i = "ABC" + "DEF"  // (A)

このようなコード(A)はコンパイル時のエラーにはなりません。

なぜなら、動的言語(=実行時にメソッドやクラスを改変できる言語)であるGroovyにおいては、(A)に行きつく前に、例えばメタクラスを用いた以下のようなコード(B)が実行されていれば、(A)は完全に正しいコードだからです。

String.metaClass.plus = { String x -> return 3 } // (B)

もちろん、(B)が実行されていなければ、(A)は実行時のエラー(GroovyCastException)になります。

確かに、(B)のように動的に演算子(もしくはplusメソッド)を追加するような処理をすることは、一般には多くはないでしょう。しかし、Groovy処理系の立場からすると、「正しいかもしれないコード」をエラーにすることはできません。この結果、(A)のような「いかにも間違っていそうな」コードをGroovy処理系はコンパイル時のエラーにはできないのです。

もう1つ例を上げます。

class MyClass {
  def foo() { "hello" }
}

MyClass a = new MyClass()
a.foa()  //  (C)

上記の(C)では、fooと書くつもりがミススペルしてfoaと書いてしまっています。

これも従来のGroovyではコンパイル時にはエラーにはならず、実行時に「メソッドfoa()がありません」というエラーになります。

これをコンパイル時のエラーにならない(できない)のも同じ理由からです。MyClassには実行時にfoa()というメソッドが注入されているかもしれない、という可能性を否定しきれない以上、Groovy処理系としては(C)をコンパイルエラーにすることはできないのです(警告は出せるかもしれません。実際、EclipseのGroovy対応では、黒い下線を表示して「疑わしい」というアピールをしたりします)。

Groovy 2.0以降で拡張された静的型チェック機能を使用すると、以下のようなAST変換アノテーションである「TypeChecked」を指定することで、アノテーションで指定された範囲内でコンパイル時の型チェックを行うことができます。

import groovy.transform.TypeChecked

class MyClass {
  def foo() { "hello" }
}

@TypeChecked
def func() {
  MyClass a = new MyClass()
  a.foa()  //  (C)
}

@TypeCheckedは、メソッドもしくはクラスに対して指定する必要があるので、上ではfunc()というメソッドを定義した上で@TypheCkeckedを指定しています。groovyコマンドでこのコードの実行を試みてみます。

$ groovy test2.groovy 
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
test2.groovy: 11: [Static type checking] - Cannot find matching method MyClass#foa(). Please check if the declared type is right and if the method exists.
 @ line 11, column 3.
     a.foa()  //  (C)
     ^

1 error

このようにコンパイル時の静的エラーとなり、"[Static type checking] -"という接頭辞が付いたエラーメッセージが表示されます。groovycコマンド(Groovyの事前コンパイラ)でコンパイルした場合も同じエラーとなります。

ここではfunc()を呼び出してはいないので、funcは実行されていないことに注意してください。確かに、「foaというメソッドが無い」ということは、実行すれば判る話であり、従来のGroovyでもそのようなエラーメッセージが実行時に表示されます。しかし、「実行していなくても型チェックができる」ことが静的型チェックの利点です。例えばEclipseなどのIDEでは、Groovyのコードを保存した時点でエラーであることが判明します。IDEの補完機能の改善にも将来的には役立つでしょう。

その代償

静的型チェックは、「ある特徴を持ったGroovyコード」をコンパイル時にエラーにし、実行させないようにすることがその唯一の機能です。「ある特徴」とは、静的型チェックのエラーを含むようなコード、ということです。つまりここでなされているのはGroovyのサブセット化であり、Groovyの意味や構文について「変更」や「追加」は行なっていません。

このことがGroovy 2.0の静的Groovyの特徴の一つです。静的型チェックもしくは静的コンパイル機能を含め「静的Groovy」はGroovyの意味上の機能追加は全くなく、むしろその削減です。さらに言うと、この点が同様の静的型言語としての拡張であるGroovy++との違いでもあります。Groovy++は静的型ならではの機能追加を積極的に試みており、その結果、通常のGroovyとの乖離を生んでおり「第二のGroovy」を目指していたように感じます。このことが「本家」Groovyの方針とは相容れず結局取り込まれなかったことの要因の一つではないかと思います。また、このことから類推すると、今後もしGroovy++でなされた機能拡張のいずれかが再度Groovy本家にも取り込まれていくとするなら、それは動的Groovyにも適用可能なものに限られるでしょう。

「静的型の観点でチェックしたときにエラーになるコード」とは、具体的に言うと、「コンパイル時、すなわちプログラムの実行を開始する前の段階で、プログラム中の任意の箇所で、任意の変数や式の型がすべて確定している前提で保守的にチェックしたときにエラーとなる」ということであり、前提として「任意の変数や式の型がすべて確定」しなければなりません。その前提を満すために、以下のGroovyの動的言語機能は なかったこと にしてチェックします。

  • カテゴリを使ったメソッド拡張
  • メタクラスを使ったメソッドやプロパティの拡張(invokeMehodの再定義やintercepterによる意味の変更を含む)

@TypeChecked指定したコード中で上のような動的拡張をしようとすることも許されませんし、TypeChecked指定した範囲外で行なわれた動的拡張は、@TypeCheck指定された範囲では効力を失ないます。

その結果、以下のような機能は@TypeCheckedされた範囲では使えなくなります。

  • ビルダー
  • カテゴリで定義されたものをuseで使うことを前提とした機能。TimeCagegoryなど。

ついでに、動的型に依存する以下も使用できません。

  • マルチメソッド(ダブルディスパッチ)。引数の型の違いでオーバーライド定義されたメソッドから、動的な引数の型を使用してメソッド解決して呼び出すこと。
  • バインディング変数の参照

なお、GDKで拡張されたメソッド群は静的Groovyでも使用することができます。GDKメソッド群は内部的にはCategoryと似た仕組み(DefaultGroovyMethods.groovy)で定義されているとはいえ、「プログラムの実行開始の前に確定している」という意味で完全に静的なものだからです。Groovy 2.0のモジュール化(参照: 8.4)で追加されたメソッドについても同様に使用できます。

静的型チェックは、このように「Groovyらしい動的機能を使えなくなる」というかなり重大な代償を伴なうことであることに注意が必要です。もちろんプロジェクトによって状況は異るとは思いますが、著者の私見では、「静的型チェック」だけではメリットが十分ではないので、次節で紹介する、性能向上をメリットとする「静的コンパイル」を通じて利用することが主となるでしょう。

Subscriber's Voice

There are no comments yet.