2012年8月3日金曜日

[Scala] break

Scala には break 文がない。
しかし break 用のクラスが用意されていて一応それっぽいコードを書くことができるようになっている。

まずは良く出てくる基本形。

  def breakSample1 {
    import scala.util.control.Breaks;
    val mybreaks = new Breaks;
    import mybreaks.{ break, breakable };

    breakable {
      println("before break");
      break;
      println("after break");
    }
    println("out of break");
  }


before break out of break

多少おまじないがあるが、一見、breakable 構文のようなものがあってそれで括っておけば break できるように見える。

import 文によるメソッド呼び出し先のオブジェクトやメソッド実行時の括弧の省略を一切無くしてみる。

  def breakSample2 {
    val mybreaks : scala.util.control.Breaks 
        = new scala.util.control.Breaks;

    mybreaks.breakable( {
      println("before break");
      mybreaks.break();
      println("after break");
    } );
    println("out of break");
  }

このように書くと、Breaks クラスの mybreaks オブジェクトに対して、 breakable メソッドを ブロック式を引数にして実行しているという構造が一目でわかる。


Breaks の実装


この break はどのように機能しているのだろう。
scala.util.control.Breaks の実装を確認してみる。
以下、scala.util.control.Breaks の実装の抜粋。

class Breaks {
  private val breakException = new BreakControl
  def breakable(op: => Unit) {
    try {
      op
    } catch {
      case ex: BreakControl =>
        if (ex ne breakException) throw ex
    }
  }
  ...
  def break() { throw breakException }
}
object Breaks extends Breaks
private class BreakControl extends ControlThrowable

break 文は専用の例外をスローしているだけ。
breakable 文では引数としてブロック式を受け取ってそれを実行し、 break 文でスローされた専用例外をキャッチしたら握りつぶして終了、という動作になっている。

一見 break っぽくなっているが、早い話が break の代わりに例外をスローして外側でキャッチするという構造になっている。


多重 break


先ほどの Breaks の実装を見ると、breakable でキャッチするのは 同一インスタンスの break でスローされた例外に限定されていることがわかる。
これを利用すると、多重脱出の break も書くことができる。

  def breakSample3 {
    val mybreaks1 = new scala.util.control.Breaks;
    val mybreaks2 = new scala.util.control.Breaks;

    mybreaks1.breakable({
      mybreaks2.breakable({
        println("before break");
        mybreaks1.break();
        println("after break");
      })
      println("out of break2");
    })
    println("out of break1")
  }


before break out of break1

「mybreaks1.break()」の後で、「"out of break2"」を飛ばして一気に「"out of break1"」に到達している。

このように多重で使う場合には、最初の省略型のようにインスタンスまで import してしまうとどちらにbreakable や break を実行しているのかわからなくなるので、Breaks クラスのインスタンスは省略できない。


breakable の無い break


breakable で括らずに break するとどうなるだろうか?

  def breakWithoutBreakable {
    val mybreaks = new scala.util.control.Breaks;
    mybreaks.break();
  }


Exception in thread "main" scala.util.control.BreakControl

スローされている例外を catch していないので当たり前といえば当たり前の結果。


コンパニオンオブジェクトの利用


Breaks にはコンパニオンオブジェクトが定義されているので、 この記事の最初に出てきた例はそのコンパニオンオブジェクトを使うともう少しシンプルになる。

  def breakSample0 {
    import scala.util.control.Breaks;
    import Breaks.{ break, breakable };

    breakable {
      println("before break");
      break;
      println("after break");
    }
    println("out of break");
  }

これならわざわざ Breaks オブジェクトを作る必要はない。
ただし コンパニオンオブジェクトはひとつなので多重 break をする場合には Breaks オブジェクトを自前で作る必要がある。

次回は、この break を使ってcontinue 相当のことを試してみる。



0 件のコメント:

コメントを投稿