日々常々

ふつうのプログラマがあたりまえにしたいこと。

GroovyのPowerAssertさんをいじめようとしてみた

GroovyのはPowerAssertという素敵なアレがあります。

assertに失敗したらそれを表示してくれるんですね。

でも文字列で表示するだけなので、toStringの実装がequals/hashCodeの実装とアレだったらこう、残念なことになります。

hogehogeなのにfalseだよ、と。こりゃ駄目だ。この辺も考えてtoString/equals/hashCodeは実装した方がいいかもしれませんのー。

文字列を表示するということは……

で、思ったんです。toStringの結果を返すなら、つまり、toStringに例外を投げさせたらPowerAssertの結果表示の時に例外でゔぉあーなことになるんじゃね?と。で。

class Hoge { public String toString() { throw new Exception() } }

いざ。

……駄目でした。涼しい顔して流されました。
「toString() threw java.lang.Exception」だってよ。ありがとよ。ちくしょう。

矛先を変えて

ちなみに、groovyshは入力された結果のtoStringを表示しますので、上記のtoStringで例外投げるようなのを実行するとスタックトレース吐きますの。

でも表示の時に出てるだけだから、処理自体はされてる。hoge単体を実行してもスタックトレース出ることからわかるかなと。

PowerAssertさんは思った以上強かったので、groovyshさんをいじめてみました。

GroovyでJavaのpackage-info.javaを一括生成するスクリプト

元ネタ: http://d.hatena.ne.jp/tbpg/20120808/1344447924

package-info.javaを全部ごそっと作ろうってのをRubyで書いたものらしいです。Ruby勉強したはずなのに殆ど覚えてない残念さを噛み締めつつ、Groovyで書いてみようと思ったのと、Gist埋め込みやってみたかったのと。

最初はFiles.walkFileTreeとかNIO.2のでやってみたんだけど、この程度のならGDKののほうがすっきりするやねーとなってしまいました。
残骸はGistの履歴に残してます。

実行結果

$ groovy generatePackageInfo.groovy 
出力完了。出力結果
./src/jp/co/package-info.java
./src/jp/co/package1/package-info.java
./src/jp/co/package1/package1_1/package-info.java
./src/jp/co/package1/package1_2/package-info.java
./src/jp/co/package2/package-info.java
./src/jp/co/package2/package2_1/package-info.java
./src/jp/co/package2/package2_2/package-info.java
./src/jp/package-info.java
$ cat ./src/jp/co/package1/package1_2/package-info.java
/**
 * jp.co.package1.package1_2パッケージ。
 *
 * <pre>
 * // TODO パッケージ内容の詳細を記述してください
 * </pre>
 *
 */
package jp.co.package1.package1_2;
$ 

Groovyのくろーじゃさん

Closureという概念が理解しにくいので色々やってみた@Groovy - (define -ayalog '())につけた長文コメントを整理しておこうかと思って書いた。

動作確認は Groovy2.0.0 です。2.0.0 って言ってみたかった。でもこの辺って多分1.7とかと変わってないと思う。

コード

とりあえずコピペ。

def counter(){
    int i=0
    return {
        i++
    }
}

メソッドの返り値は何?」

counterメソッドのreturnは { i++ } です。
これはクロージャインスタンス作ってるののシンタックスシュガーです。日本語でおk。でも日本語で言っても伝えられる自信が全くない。だからコードで書くね!

// これは
return { i++ }

// だいたいこういうこと
Closure c = new Closure(null) {
  Object call() {
    return i++;
  }
}
return c;

なるべくJavaっぽく書いてみた。これに置き換えても動きます。

で、呼び出しについて。

// これは
println c()

// だいたいこういうこと
println c.call();

Closureのcall呼び出すのって多いので、省略していきなり実行出来るように見せかけてるだけです。だから省略してるのとしてないのの組み合わせでも同じ挙動。

def c = {'a'}

assert c() == 'a'
assert c.call() == 'a'
assert c.doCall() == 'a'

うん。よく使うものは省略しとこうぜーなのがGroovyだと思ってだいたい間違いない。だから普通は一番上の一番短い書き方する。

クロージャの引数

クロージャは引数とります。受けたいだけ並べれば良いです。仮引数名を -> の前に書くです。

def c = { a, b, c ->
  a + b + c
}

assert c(1,2,3) == 6

これ省略すると、引数1つをとるクロージャになります。何も書かなきゃ0個…ではなく、1個なんですね。なお、省略時の仮引数名は it になります。アレです。

def c = { println it }

c()     // 'null'
c(1)   // '1'

見ての通り、呼ぶ方は省略出来ちゃうので紛らわしい。引数1個のクロージャを引数0個で動かすために無理矢理 null 突っ込んで呼んでるだけです。

0個にしたいときは0個にする必要があります。 -> の前に何も書かない。これで0個になります。

def c = {-> 'abc'}

assert c() == 'abc'

仮引数を明示的に指定して作ったクロージャに引数渡すと例外になります。0個で作ったら1つでも渡すと例外だし、3つで作って2つしか渡さなかったら例外です。
仮引数を省略して作ったクロージャに引数2つ渡すと例外になります。0個か1個です。

まとめ

Groovyは色々省略出来るけど、どうしてそう省略できてるかをある程度把握しないと、何でそうなるのか理解出来なくて困ることもたまにあります。たまに。……結構?たぶん、たまに。
かといって省略せずにやるとGroovyの力は半減どころじゃないですし……使ってるうちにわかるもんですかねー。

なお、ここで書いてるのは現時点での私の理解であり、間違ってるところとか誤摩化してるところとか多分あります。ごまかしはニヨニヨしといてください。間違ってたらこっそり教えてください。