ファンクションの上からスタックを見下ろす
動機
Iterable.foreachなど、関数を引数にとるメソッドの実装を知りたい。
予備知識
必要な知識はこのへん。
- メソッド呼び出しはスレッドごとにスタックを積む
- 関数呼び出しはFunction1.applyを実装したメソッドの呼び出しである
- Throwableをnewすると、その時に積まれているスタックがスタックトレースに入る
(ただしVM, コンパイラ依存)
参考: 過去ログ
http://d.hatena.ne.jp/kya-zinc/20090221/1235237021
方針
Iterable.foreachに関数を渡して呼び出すと、
呼び出し元のスタック -> foreach呼び出し -> 関数呼び出し
の順にスタックが積まれるはず。
したがって、関数の中でThrowableをnewしてトレースを吐かせると
foreachでどのようにスタックが積まれるかが分かる。
実験1 Array.foreach
- Array.foreachを見る。
Array.foreachに、Throwableを作ってトレースを吐くだけの関数を渡す。
他のコレクションにはさほど特筆すべき点はなし。
まあ、標準ライブラリのpure Scalaな部分はソースを読めという話である。
object ThrowableTest { import collection.mutable import collection.immutable def main (args : Array[String]) : Unit = { Array(1).foreach{_ => new Throwable().printStackTrace(System.out) } val cols : List[Iterable[Any]] = List( Array(1), List(1), mutable.Set(1), immutable.Set(1), mutable.Map(1 -> ""), immutable.Map(1 -> ""), {val q = new mutable.Queue[Int];q.enqueue(1);q}, {new immutable.Queue[Int].enqueue(1)}, {val s = new mutable.Stack[Int];s.push(1);s}, {new immutable.Stack[Int].push[Int](1)} ) for(col <- cols){ col.foreach{_ => // println("====" + col.toString + "====") // new Throwable().printStackTrace(System.out) } } } }
実行結果
java.lang.Throwable at ThrowableTest$$anonfun$main$1.apply(ThrowableTest.scala:5) at ThrowableTest$$anonfun$main$1.apply(ThrowableTest.scala:5) at scala.Iterator$class.foreach(Iterator.scala:414) at scala.runtime.BoxedArray$AnyIterator.foreach(BoxedArray.scala:45) at scala.Iterable$class.foreach(Iterable.scala:256) at scala.runtime.BoxedArray.foreach(BoxedArray.scala:24) at ThrowableTest$.main(ThrowableTest.scala:5) at ThrowableTest.main(ThrowableTest.scala) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at scala.tools.nsc.ObjectRunner$$anonfun$run$1.apply(ObjectRunner.scala:75) at scala.tools.nsc.ObjectRunner$.withContextClassLoader(ObjectRunner.scala:49) at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:74) at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:154) at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
ScalaのArrayは、JavaVMの配列にコンパイルされるらしい。
Array(class).applyやArray.updateはこれでよいのだが、
Javaの配列がScalaのArrayほど豊富なメソッドを備えているわけもなく、
BoxedArrayというラッパーが必要らしいことがわかる。
Javaのオートボクシング、アンボクシングの要領か。
実験2 Actor.receiveとActor.react
- Actor.receiveとActor.reactの違いを見る。
receiveするActorとreactするActorを作り、
少し待ってからそれぞれにメッセージを送る。
object ThrowableTest2 { import actors.Actor._ def main(args : Array[String]) : Unit = { def printStack() = new Throwable().printStackTrace(System.out) val actor_receive = actor{ receive{ case _ => { println("====receive====") printStack() } } } val actor_react = actor{ react{ case _ => { println("====react====") printStack() } } } //wait actor to invoke receive(react) Thread.sleep(100) actor_receive ! "hoge" Thread.sleep(100) actor_react ! "fuga" } }
実行結果
====receive==== java.lang.Throwable at ThrowableTest2$.printStack$1(ThrowableTest2.scala:5) at ThrowableTest2$$anonfun$1$$anonfun$apply$1.apply(ThrowableTest2.scala:10) at ThrowableTest2$$anonfun$1$$anonfun$apply$1.apply(ThrowableTest2.scala:7) at scala.actors.Actor$class.receive(Actor.scala:438) at scala.actors.Actor$$anon$1.receive(Actor.scala:93) at scala.actors.Actor$.receive(Actor.scala:148) at ThrowableTest2$$anonfun$1.apply(ThrowableTest2.scala:7) at ThrowableTest2$$anonfun$1.apply(ThrowableTest2.scala:7) at scala.actors.Actor$$anon$1.act(Actor.scala:94) at scala.actors.Reaction.run(Reaction.scala:76) at scala.actors.Actor$$anonfun$start$1.apply(Actor.scala:784) at scala.actors.Actor$$anonfun$start$1.apply(Actor.scala:782) at scala.actors.FJTaskScheduler2$$anon$1.run(FJTaskScheduler2.scala:165) at scala.actors.FJTask$Wrap.run(Unknown Source) at scala.actors.FJTaskRunner.scanWhileIdling(Unknown Source) at scala.actors.FJTaskRunner.run(Unknown Source) ====react==== java.lang.Throwable at ThrowableTest2$.printStack$1(ThrowableTest2.scala:5) at ThrowableTest2$$anonfun$2$$anonfun$apply$2.apply(ThrowableTest2.scala:19) at ThrowableTest2$$anonfun$2$$anonfun$apply$2.apply(ThrowableTest2.scala:16) at scala.actors.Reaction.run(Reaction.scala:78) at scala.actors.FJTask$Wrap.run(Unknown Source) at scala.actors.FJTaskRunner.scanWhileIdling(Unknown Source) at scala.actors.FJTaskRunner.run(Unknown Source)
読みにくい。
とりあえず
- receiveはreceiveの中で関数が実行される
- reactではreactを実行したスレッドではない別スレッドによって実行される
ことくらいは読みとれる。