uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

[groovy] Groovy 2.0と静的型チェック

先週にリリースされたGroovyの1.9系の最新版バージョンは、1.9 beta 5かと思いきやなんと、2.0 beta 1となる、とのことでした。リリースノートはこちら。今後1.9系はなくなることになります。

なんでこうしたかというと、1.xxのxxを増やすのが限界*1なので、これからは、バージョン付けの規約を変更して一年に1つメジャーバージョンアップする、ということだそうです。

いままでも将来のロードマップとして2.0というのは語られてきた(MOP 2.0とか)わけですが、いつか将来の"2.0"はおいといて、具体的に次のを2.0にしましたヨロシクとのことです。ちなみに2.0の正式リリースは2012年初予定とのこと。

ちょっとびっくりしました。

さて以上は前置きで、1.9 beta5あらため2.0beta1ですが、目玉は、"Grumpy Mode"という愛称で*2親しまれてきた(?)静的型チェック(Statically Type Check, STC)がいよいよメイントランクに取り込まれたということでしょう(Grumpy Mode。の経緯についてはこちらの記事もどうぞ)。STCについて、くわしくはリリースノートや、GEP8を参照ください。

今回、2.0 beta1をダウンロードしてSTCを試してみました。

以下、ドキュメントなどを参考に試してみたコード。「//[Static type checking]」というコメントがSTCによって表示されるエラーメッセージで、実際にはコンパイルエラーメッセージとなります。これらのコンパイルエラーメッセージは、groovyコマンドでも出ますが、groovycコマンドでも表示させることができます。

@TypeCheckedをつけた範囲がSTC対象となり、それ以外は従来と同様です。

import groovy.transform.TypeChecked

class A {
   int x
}

def method(){'String'} //OK

@TypeChecked
int method2() {
   'String' // [Static type checking] - Cannot return value of type java.lang.String on method returning type int
}

@TypeChecked
void method(String message) {
  A obj = new A()
  obj.y = 2 // [Static type checking] - No such property: y for class: A -> A
  int x=2
  x = 'String'//[Static type checking] - Cannot assign value of type java.lang.String to variable of type int

  String s = 'string'+1 // OK
  
  long myLong = 10L
  int myInt = myLong   //[Static type checking] - Possible loose of precision from java.lang.Long to java.lang.Integer
  myInt = (int)myLong   // OK
  
  int n1 = 2L // OK

  int n2 = 2 // 2g  // ... OK

  int n3 = method() //[Static type checking] - Cannot assign value of type java.lang.String to variable of type int
}

このように、実行しなくてもコンパイル時にかなり型エラーチェックしてくれます。
以下のようにフロー解析に基づいた型推論もしてくれるようです。

import groovy.transform.TypeChecked

@TypeChecked test() {
    def x = "abc" // (1)
    x = 1 // OK
    println x.toUpperCase() //  [Static type checking] - Cannot find matching method int#toUpperCase()
    x = "def" // OK
    println x.toUpperCase() // OK
}
test()

これが「フローセンシティブタイピング」ってやつかしら。(1)でxの型がStringに定まるわけではなく、フロー解析で実行時の型を推測してチェックするらしい。ScalaとかMLの型推論とは明らかに違いますね。グローバルなフロー解析をするかどうかは不明(たぶんできないと思うが…)。

GroovyのSTCは、Groovy++のそれとはかなり違います。Groovy++では、静的型チェックと静的コンパイルは一体だったのに対して、Grumpyは型エラーのチェックのみで、GCCでいうところの「-wall -ansi-pedanticオプション」とか、あるいはlintのようなイメージです。また、Groovy++のように言語のセマンティクスを変更するということは一切していません。2.0beta1での生成バイトコードもちょっとだけ見てみましたが、見た限りは変わってません。つまり今のところは型情報は最適化・性能向上には使っていないということでしょう(今のところは、です。将来は使うと思います)。

なお、ジェネリクスについては、STC型チェックの対象情報ではあるものの、今のところ完璧ではない様です。でもとりあえず「ジェネリクスは飾り」ではなくなりました*3

import groovy.transform.TypeChecked

@TypeChecked
def method()
{
  List<Integer> list = new ArrayList<Integer>();
  list.add(3)
  list.add("abc") //[Static type checking] - Cannot find matching method java.util.ArrayList#add(java.lang.String)

  list << 1 // OK
  list << "abc" // OK!
  list += 1  // NG
  println list
}

なお、STCが導入された理由は、Groovyの使われかたとして、「簡潔なJava」「便利なJava」として使われることが多いからとのこと。Groovyの動的性質を使わない場合には、Grumpyモードは有用でしょう。ただし、GrailsなどのGroovyの動的性質を十分に使ったフレームワークでDomain Classを操作するコードやDSLが、GrumpyモードのGroovyで記述できるかどうかは疑問です。もしそれができるようになるとするなら、DSLD(DSL Descriptor)と組み合せたチェック機構になるのかもしれません。

ちなみに、DSLDというのは、STS版EclipseのGroovyモードで使われている技術で、ダイナミックメソッドとかビルダーのメソッドなどをコードチェックやコード補完で利用できるようにする仕組みです。さらに、最新版のSTS 2.8.0では、「Groovy Type Check」という機能が実験的に追加されているのですが、これは限定的ながらもGroovyの静的型チェックをしてくれるものであり、将来的にはきっと、このSTCに基づいたものになることでしょう。

さらにちなみに、DSLD(もしくはそのIntelliJ版であるGDSL)は、GEP9 Groovy Modularizationでも言及されており、モジュールがDSLを定義するなら、そのモジュールはDSLDを含めることができるようになるようです。

*1:今までの方針のままだとすると、1.8, 1.9, 1.10, 1.11…とかになってしまいますが、1.11は数学的には1.1+0.01となりよろしくないと。

*2:grumpy modeはコードネームで正式名称ではないとのこと。

*3:もともと飾りではない。Groovyで付与した型情報は、Javaからは完全に情報として利用される。ただしGroovyコードからはジェネリクス情報はAST変換の一部(@ListenerList)を除き、ほとんど使われていなかった。