uehaj's blog

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

groovyクロージャについて徒然と

groovyのクロージャをブロックとして使っていると、breakとかcontinueとかができませんのできっと誰もが最初はあれ?と思ったはず。

理由は単純で、クロージャというのは無名メソッド定義であって、たまたま平たくコード中にならんでいて、スコープ則にしたがって変数参照ができますが、実体は別メソッドなんですね。

eachのループからbreakしたければ例外をthrowするか、元になるコレクションをあらかじめgrepとかでフィルタリングしてやるのも手です。continueしたい場合はreturnするとループの次の回に入ります。
多重のループ中から脱出したりするのにも、例外を使うのも考慮しても良いかもしれません。

ここらへん、RubyのProcのように、まるで本当にブロックのように振舞うように機能を追加することは、可能なのかもしれません。ただそれがないゆえの単純さがGroovyのクロージャにはあります。表記もこれ以上ないって言うぐらい簡潔だし。

閑話休題。

Groovyクロージャには、暗黙引数のitってのがありますが、これってちょっと不思議な存在。一般には、クロージャの引数は厳密に引数チェックされます。引数が1つなら1つ、ゼロ個ならゼロ個でしかcallできない。しかし、例えば

Closure c = {println it}

のとき、

c.call("hello")
c.call()

のいずれでも呼び出せます(後者の場合itはnullになる)。これが意味するのは、

Closure c = { 〜 }

Closure c = {-> 〜 }

の略記法ではないということです。もしそうなら、c("hello")は引数の個数が異なる旨のエラーになるはずですがそうはならないからです。同様に

Closure c = {it-> 〜 }

の略記法でもない。そうならc()がエラーになるはずだからです。
itはそれほどまでに特別扱いされているのか?という気になりますが、考え方としては、引数を省略したクロージャは

Closure c = {def ...arg-> 〜}

のように可変引数もしくは

Closure c = {Object[] arg-> 〜}

のように配列引数クロージャであって、itは「arg.size()==0?null:arg[0]」とかの略記法と考えるのがいいのではないかと。

(追記)とおもったけど、

Closure c={println(it)}
c.call(1,2,3)

は「引数がマッチしない」エラーになるんだね。「0 or 1個引数」という(Groovy自身では定義できない)クロージャが存在するのか・・・。ちときもいな。

(追記)コメント欄で指摘いただきましたが、

{it=null-> 〜}

のようにデフォルト引数と考えればOKですね。
すっきりしました。ありがとうございます。