初学者向けでない Scalaバッドノウハウ(1) - traitの初期化時にManifest型の値を取得する
ネタが尽きたわけではないが、Manifest絡みで嫌な現象に遭遇したのでメモと回避方法(現在はManifest
型をつかうべきではないがそれはおいておく)。
まず、問題として、次のようなコードを書くと実行時例外(unknown error
)が発生する(Scala 2.10.4)。
trait M[T] { implicit val m: Manifest[T] } case class Point(x: Int, y: Int) object Main extends M[Point] { implicit val m: Manifest[Point] = manifest[Point] def main(args: Array[String]) { json4sObj.extract[Point] } }
このようになる原因は、traitでの初期化でmanifest
メソッドを呼び出すとnull
が返ってくることにあるようだ(manifest
メソッドはscalacで特別な扱いを受けていることに関連していると思われる)。それならと、manifest
呼び出しのタイミングを遅らせるように
trait M[T] { implicit def m: Manifest[T] } case class Point(x: Int, y: Int) object Main extends M[Point] { implicit def m: Manifest[Point] = manifest[Point] def main(args: Array[String]) { json4sObj.extract[Point] } }
としてみると、今度は
at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8)
というStackOverflowErrorが発生する。これの回避策だが、
implicit def m
の部分を
implicit def m()
に書き換えればOK。つまり、最終的なコードは以下のようになる。
trait M[T] { implicit def m(): Manifest[T] } case class Point(x: Int, y: Int) object Main extends M[Point] { implicit def m(): Manifest[Point] = manifest[Point] def main(args: Array[String]) { json4sObj.extract[Point] } }
これでうまく行く理由、というより、一つ前のコードがうまく行かなかった理由が釈然としないのだが…(コンパイラが暗黙に想定しているimplicitなManifest[T]
を上書きしてしまったせいではないかと推測している)