君は必ずmainのところに戻ってくる
(8/5更新)
- 実験4を追加。
概要
Scalaでは、遅延評価が許されているので
returnがメソッドの実行ではない場所で評価されることがある。
これで遊んでみる。
実験1: foreachの中でreturn
まずは簡単な使い道。foreachに渡すブロックの中でreturnする。
package returntest object ReturnInFor { def main(args : Array[String]) : Unit = { for(i <- List(1,2,3,4)){ println(i + "th block call") val st = new Throwable().getStackTrace() println(st(0)) return } } }
実行結果
1th block call returntest.ReturnInFor$$anonfun$main$1.apply(ReturnInFor.scala:7)
1stではないのはご愛敬。
スタックトレースを見て分かるように、
returnが評価されるときに実行されているコードは、
無名関数のapplyであり、mainではない。
しかしreturnの効果としてはmainのreturnになり、
2回目以降のブロック呼び出しはなされない。
結論
returnは、returnが含まれているメソッド呼び出しのreturnになる。
実験2: singletonオブジェクトのフィールドにmainのreturn
mainの中で、returnのみを実行するブロックを作成し、
singletonオブジェクトのフィールドに関数として格納しておく。
これでどこからでもreturnが実行できるという寸法。
これで例外を出さずに強制終了だ!
スタックトレースが嫌いな悪い子は使ってみよう!
package returntest object GlobalReturn { //global return function var returnf : Unit => Unit = null def main(args : Array[String]) : Unit = { //set returnf in main returnf = (_ : Unit) => { return } //another thread val th = new Thread(){ override def run() { //thread call returnf println("thread call return") returnf() println("thread end") } }.start() //returnf in method println("method call") AnotherObject.method() println("main end") } } object AnotherObject { def method() = { println("method start") try { println("method call return") GlobalReturn.returnf() } finally { println("finally") } println("method end") } }
実行結果の例(スレッドの実行順序によって多少異なる)
thread call return Exception in thread "Thread-0" scala.runtime.NonLocalReturnException method call method start method call return finally
- AnotherObject.methodの中でreturnf()を呼んでも、mainが終了していることがわかる。
- finallyは実行されている。
- mainスレッドが作った別スレッドからreturnf()を呼ぶと、scala.runtime.NonLocalReturnExceptionという面白げな例外が発生する。
Nonときたか。
実験3: 再帰呼び出しの好きなところにreturn
メソッドの再帰呼び出しがなされるたびに
returnするブロックを関数にしたものを、配列に格納しておく。
再帰呼び出しを終わらせたい時に、ランダムで1つ関数を選び、呼びだす。
さあて、どこまでreturnするのかな?
package returntest import scala.util.Random object RecReturn { def main(args : Array[String]) : Unit = { recCall(20) } def recCall(n : Int) = { if(n < 0){ throw new IllegalArgumentException } //blocks [0-n] rec(n, new Array[Unit => Unit](n + 1)) } def rec(n : Int, returns : Array[Unit => Unit]) : Unit = { println("call with " + n) //set block in array returns(n) = (_ : Unit) => { println("return @ " + n) return } if(n == 0){ val rand = new Random(System.currentTimeMillis).nextInt(returns.length) //call random block returns(rand)() } //recursive call rec(n-1, returns) println("call " + n + " end") } }
実行結果の例(Random.nextIntの値により異なる)
call with 20 call with 19 call with 18 call with 17 call with 16 call with 15 call with 14 call with 13 call with 12 call with 11 call with 10 call with 9 call with 8 call with 7 call with 6 call with 5 call with 4 call with 3 call with 2 call with 1 call with 0 return @ 13 call 14 end call 15 end call 16 end call 17 end call 18 end call 19 end call 20 end
- 再帰呼び出しは20から0まで行われている。
- 呼びだし13を強制returnしたらしい。
- 14以降は普通に終了している。
かくして、どこまで戻るか分からないreturnが完成した。
(以下8/5追記)
実験4 メソッド終了後にreturn
先日は別スレッドを使ったが
リファレンスマニュアルを見ると
「returnが含まれるメソッドが終了してからreturnを呼ぶと
scala.runtime.NonLocalReturnExceptionが出る」
という趣旨のことが書いてあるので
そのパターンを試してみた。
package returntest object AfterReturn { var returnf : Unit => Unit = null def main(args : Array[String]) : Unit = { setReturn() try { returnf() } catch { case e => e.printStackTrace(System.out) new Throwable().printStackTrace(System.out) } println("main end") } def setReturn() { returnf = (_ : Unit) => { return } } }
実行結果
scala.runtime.NonLocalReturnException java.lang.Throwable at returntest.AfterReturn$.main(AfterReturn.scala:12) at returntest.AfterReturn.main(AfterReturn.scala) main end
確かに例外を出せることを確認。
scala.runtime.NonLocalReturnExceptionは補足できるが
スタックトレースを作ってくれないことがわかった。