ファンクションの上からスタックを見下ろす

動機

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を実行したスレッドではない別スレッドによって実行される

ことくらいは読みとれる。