戻り値の型が型引数に依存してる関数について

まず戻り値の型が型引数に依存してるってどういうことかっていうと、こういう感じ

def foo[A]( param :String ) : A = { ... }


依存してるって言い方が一般的なのか、あってるのかは知りませんというか、勝手にこう呼んでます。つまり、

  1. foo[Int]( "scala" ) というように、明示的に型引数を書く
  2. val obj:Int = foo( "scala" ) のように、戻り値の型が推論してもらえるような場所に書く
  3. 上記と結局は同じだが、 def bar( param: Int ) という関数があり bar( foo( "scala" ) ) という呼び方をする

というような使い方をしないといけない関数のことです。

これを

val obj = hoge("scala")

というように呼ぶと、objがNothing型に推論されてしまいます。

まぁ普通コンパイル時にNothing型になっている時点で、使い方間違ってることに気づくはずですが・・・

しかし、気づかずにそのまま実行した場合、というかよくREPLで試してる場合にやらかすんですが、REPL上で

scala> hoge("scala")

と単に呼び出した場合に、以下のような一見意味不明なエラーが出ます

java.lang.ClassCastException: java.lang.String cannot be cast to scala.runtime.Nothing$

はじめて見た場合は(゚Д゚)ハァ?って感じですよね。
というか、そもそも上記の様に呼び出した場合コンパイル時にエラーにしてくれてもいい気もしますが・・・。*1
Nothingってclassは実態が存在しないはずなのに、実行時にどうしようもなくなると(?)scala.runtime.Nothing$っていうものにcastしようとするみたいです。

それで、まえからこのエラーメッセージ分かりにくいけどどうしようもないよなーと思っていたんですが、解決方法がわかったお(`・ω・´)という話。

わかったといっても、あるライブラリのなかにうまくやっているのを見つけただけですが。
以下、該当部分をそのままコピペ

def getAs[A <: Any: Manifest](key: String): Option[A] = {
  require(manifest[A] != manifest[scala.Nothing],
    "Type inference failed; getAs[A]() requires an explicit type argument " +
    "(e.g. dbObject.getAs[<ReturnType>](\"somegetAKey\") ) to function correctly.")

  underlying.get(key) match {
    case null => None
    case value => Some(value.asInstanceOf[A])
  }
}

型引数をManifestにして、引数のチェック時に、型がNothingじゃないことを確認すれば、だいぶマシなエラーメッセージがだせるね(・∀・)! っていうだけです。(コンパイル時ではなく実行時だけど・・・)

もし、戻り値の型が型引数に依存する関数を書く場合*2は、こういう書き方をするようにしましょう!

*1:すごくがんばれば、自前でそういうことできるのかな・・・?

*2:あまりそういう機会ないかもしれないけど