日々常々

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

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の力は半減どころじゃないですし……使ってるうちにわかるもんですかねー。

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