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)
}
}