Scala 2.8ではライブラリレベルでbreakがサポートされることになっていて、以下のような感じで使える:
breakable { for(i <- 1 to 10) { if(i >= 5) break println(i) } }
これはこれで便利だし大変結構なのだけど、このライブラリレベルbreak、どのbreakableから脱出するかが動的スコープに基づいて決定されるため、ユーザの意図しない挙動になることがあり問題ではないか、という問題提起が、ついこないだ、scala.internals MLでなされていた。問題提起をしたLex Spoon氏は代案として、returnを使った、次のような静的スコープのbreakを提案している:
def breakable(f: (Unit=>Nothing) => Unit) { f(() => return) } breakable { break => for (x <- Products) { if (x > 5) break() println(x) } }
どのbreakableから脱出するかを無名関数の引数として明示的に受け渡しする分、若干冗長だが、確かにこれで静的スコープになる。
ただ、これにも問題はあって、break()と書くべきところをbreakとtypoしてもコンパイルエラーにならない、という指摘がなされている。で、この静的スコープ版breakの問題点をどう解決するかをちょっと考えていたのだが、意外に簡単に解決できることがわかった。実装としては、上のものとほとんど同じで、
- fの型を(Unit => Nothing) => Unitから、(=> Nothing) => Unit (ByNameFunction型)に書き換える
- fに対して、returnの呼び出しを含む無名関数を渡して呼び出す代わりに、returnの呼び出しを直接渡して呼び出すようにする
という変更を行うだけ。
def breakable(f: (=> Nothing) => Unit) { f({ return }) }
使い方は以下のような感じ。
breakable{break => for(i <- 1 to 10) { if(i >= 5) break // break()と書くとコンパイルエラー println(i) } }