まだの方は 実際にあった scala.App の怖い間違い からご覧ください。
何が変なの?
最後のコードを repl で動かしてみます。
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait SuperMain extends App {
def printClassName(): Unit
println("Start")
printClassName()
println("Finish")
}
object Main1 extends SuperMain {
def printClassName(): Unit = println("Main1")
}
object Main2 extends SuperMain {
def printClassName(): Unit = println("Main2")
}
// Exiting paste mode, now interpreting.
defined trait SuperMain
defined object Main1
defined object Main2
scala> Main1.main(Array())
Start
Main1
Finish
ちゃんと動いている様に見えます。ここで続けてもう一度 Main1.main(Array())
を実行してみます。
scala> Main1.main(Array())
// なにも表示されない!?
2度目以降の main()
呼び出しでは何も表示されません。 SuperMain
にまとめる前の次のコードでは動いていたのに。
scala> :paste
// Entering paste mode (ctrl-D to finish)
object Main1 extends App {
def printClassName(): Unit = println("Main1")
println("Start")
printClassName()
println("Finish")
}
object Main2 extends App {
def printClassName(): Unit = println("Main2")
println("Start")
printClassName()
println("Finish")
}
// Exiting paste mode, now interpreting.
defined object Main1
defined object Main2
scala> Main1.main(Array())
Start
Main1
Finish
scala> Main1.main(Array()) // 2度目も動く
Start
Main1
Finish
SuperMain
で共通化したコードでは、実は Main1
object を参照したタイミングで println()
が実行されています。 main()
を実行しても何も起こりません。
なぜそうなるか?
なぜ main()
呼び出し時に println()
が実行されないか探るために、 App.scala を見てみましょう。注目したい点だけを抜き出すと次のようになっています。
trait App extends DelayedInit {
private val initCode = new ListBuffer[() => Unit]
override def delayedInit(body: => Unit) {
initCode += (() => body)
}
def main(args: Array[String]) = {
// ...
for (proc <- initCode) proc()
// ...
}
}
DelayedInit
を継承し、 delayedInit()
で initCode
に処理をあつめ、 main()
で実行しています。
Scala言語仕様 5.1 の Delayed Initialization に DelayedInit
の説明があります。
The initialization code of an object or class (but not a trait) that follows the superclass constructor invocation and the mixin-evaluation of the template's base classes is passed to a special hook, which is inaccessible from user code. Normally, that hook simply executes the code that is passed to it. But templates inheriting the scala.DelayedInit trait can override the hook by re-implementing the delayedInit method,
「object または class の初期化コード (ただし trait は除く)は、」 という書き出しが目に止まったでしょうか?
そうです。trait の初期化コードは delayedInit()
には渡されない。 delayedInit()
に渡されなければ、App.scala
で initCode
に追加されることもなく main()
呼び出し時に実行されることもありません。
どうすれば良かったか?
似たような処理が複数箇所にあるのは良くないので、共通の親クラス(
SuperMain
)にまとめます。
と言いながら trait SuperMain
にしたのが原因です。 class なら大丈夫なはずです。試してみましょう。
scala> :paste
// Entering paste mode (ctrl-D to finish)
// trait ではなく class にする
abstract class SuperMain extends App {
def printClassName(): Unit
println("Start")
printClassName()
println("Finish")
}
object Main1 extends SuperMain {
def printClassName(): Unit = println("Main1")
}
object Main2 extends SuperMain {
def printClassName(): Unit = println("Main2")
}
// Exiting paste mode, now interpreting.
defined class SuperMain
defined object Main1
defined object Main2
scala> Main1.main(Array())
Start
Main1
Finish
scala> Main1.main(Array()) // 二度目も実行される
Start
Main1
Finish
怖いですね。こんなこと気にしなければいけないくらいなら、自分は extends App
の代わりに def main(args: Array[String]): Unit = {}
と書きます。たいしてタイプ数変わらないしね。