kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

初学者向けでない 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]を上書きしてしまったせいではないかと推測している)