kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Scalaで静的スコープ(かつ、間違った使い方をしにくい) breakの実装

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