uehaj's blog

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

[groovy][groovy1.9] begin、endメソッドは特別扱い

Groovy 1.9 SNAPSHOT*1では、スクリプトで定義したbegin,endメソッドは、-p,-nオプションをつけて実行されるとき、特別扱いされます。

つまりたとえば、beginendtest.groovy:

def begin(){
  total = 0
}
total += line.split(" +")[4] as int
def end(){
  println total
}

というコードを用意しておくと、これは第五フィールドを合計するものですが、

$ ls -l | grep -v '^total' | /tool/groovy-1.9-trunk/groovy-core/target/install/bin/groovy -n beginendtext.groovy
4108

のようにls -lの結果からファイルサイズを合計できます。

さて、ちなみに、メソッドだけなのか、と疑問がわきます。というのも、上のbegin/endを呼びだしているところは、groovy-core/src/main/groovy/ui/GroovyMain.javaのprocessReader()というメソッドなのですが、以下のように

        try {
            InvokerHelper.invokeMethod(s, "begin", null);
        } catch (MissingMethodException mme) {
            // ignore the missing method exception
            // as it means no begin() method is present
        }
        try {
            InvokerHelper.invokeMethod(s, "end", null);
        } catch (MissingMethodException mme) {
            // ignore the missing method exception
            // as it means no end() method is present
        }

begin,endがクロージャであっても呼べそうな気がしてくるのです。ではちょっと、以下のようなbeginendtest2.groovyを用意してやってみます。

begin = {total = 0}
total += line.split(" +")[4] as int
end = { println total }

やあ

$ ls -l | grep -v '^total' | /tool/groovy-1.9-trunk/groovy-core/target/install/bin/groovy -n beginendtext2.groovy
Caught: groovy.lang.MissingPropertyException: No such property: total for class: beginendtext2
groovy.lang.MissingPropertyException: No such property: total for class: beginendtext2
	at beginendtext2.run(beginendtext2.groovy:2)

あれ呼べない。もうちょっと単純にしたbeginendtest3.groovyを用意し:

begin = { println "BEGIN" }
println line
end = { println "END" }

で、たあ。

ls -l | grep -v '^total' | /tool/groovy-1.9-trunk/groovy-core/target/install/bin/groovy -n beginendtext3.groovy
-rw-r--r--  1 uehaj3  staff     97  7  2 11:55 beginendtext.groovy
-rw-r--r--  1 uehaj3  staff     86  7  2 12:00 beginendtext2.groovy
-rw-r--r--  1 uehaj3  staff     66  7  2 12:02 beginendtext3.groovy
END

なぜかendだけ呼ばれていることがわかります。なんでだっ!!! なんでbeginだけがクロージャで設定できないんだ! と悩みましたが、わかってみると、答えは簡単でした。

begin = { println "BEGIN" }  // (1)
println line                         // (2)
end = { println "END" }       // (3)

の(1)、(3)の代入文は、(2)と同様に、入力の毎行に実行されるのです。そして、beginをInvokeHelperで呼び出そうとする時点では、(1)は実行されていないのです。なので、begin()の呼び出しが成功するわけはありません。

そうとわかれば、解決策は、

import groovy.transform.Field
@Field begin = {total = 0}
total += line.split(" +")[4] as int
@Field end = {println total}

こうだ!!!
groovy.transform.*はデフォルトでimportして欲しいものです。

*1:今のところ自分でコンパイルするしかない。[http://groovy.codehaus.org/Building+Groovy+from+Source:title=こちら]を参照。