TypeTag・ClassTagを使わないといけない時がやってきたので、自分用メモ。
TypeTag・ClassTagとは
JVM言語は、コンパイル時に型パラメーターの情報が削除される 。
実行時に型パラメーターの情報などが欲しい場合に使う。
コンパイル時に取得可能な全ての型情報を実行時に持ち込むオブジェクトである。
例えば、以下のコードでは、T型の情報はコンパイル時には消去されており、T型はランタイム時に運ばれない。
def getType[T](x: List[T]): Unit = {
}
なので、以下のコードのように、パターンマッチで型で振り分けしようとするとwarningが出てしまう。
なぜなら、Tと言う型情報はコンパイル時に消去されており、ランタイム時の型情報(今回の場合は、List("ss")なので、String型)を受け渡せないからだ。
def getType[T](x: List[T]): Unit = x match {
case _: List[String] => println("String")
// non-variable type argument Int in type pattern List[Int] (the underlying of List[Int]) is unchecked since it is eliminated by erasure
case _: List[Int] => println("Int")
// non-variable type argument String in type pattern List[String] (the underlying of List[String]) is unchecked since it is eliminated by erasure
}
getType(List("ss"))
そこで、コンパイル時に型情報を保持したい場合には、TypeTag・ClassTagオブジェクトを利用する。
TypeTagを使う事で、コンパイル時にT型の情報を格納したTypeTagオブジェクトを作るので、ランタイム時にT型情報(今回で言うと、String)を受け渡せる。
import scala.reflect.runtime.universe._
def getType[T](x: List[T])(implicit tag: TypeTag[T]): Unit = x match {
case _: List[String] => println("String")
case _: List[Int] => println("Int")
}
getType(List("ss"))
TypeTag
全ての型情報を所持保持する。
例えば、以下のコードの場合には完全に型情報を保持する。
List(44)の場合は、List[Int]と全ての型情報を保持する。
tpeメソッドを使えば、型情報を取得できる。
import scala.reflect.runtime.universe._
def getType[T](x: T)(implicit tag: TypeTag[T]): Unit = {
println(tag.tpe)
// => List[Int]
}
getType(List(44))
ClassTag
削除されたクラス型のみ保持する。
例えば、以下のコードの場合には消去されたListクラスのみ保持する。
import scala.reflect._
def getType[T](x: T)(implicit tag: ClassTag[T]): Unit = {
println(tag)
// => scala.collection.immutable.List
}
getType(List(44))
気をつける点
型が明示的になっていない場合は利用できない
val request: Request[T]になっており、コンパイルする時点で型が確定されていません。
なので、getTypeメソッドでTypeTag[T]を指定していても、型情報が保持されていないので(実際にはT型を保持)
getTypeメソッドで型情報を取得できない。
case class Request[T](value: T)
def createRequest[T](value: T): Unit = {
val request: Request[T] = Request(value)
getType(request.value)
}
def getType[T](value: T)(implicit tag: TypeTag[T]) = {
println(tag.tpe)
// => 実行エラー
// No TypeTag available for T
}
createRequest(44)
しかし以下の例ように、val request: Request[Int] = createRequest(44)ように、request[Int]と、明示的に型を指定しておけば問題ない。
case class Request[T](value: T)
def createRequest[T](value: T): Request[T] = {
Request(value)
}
def getType[T](value: T)(implicit tag: TypeTag[T]) = {
println(tag.tpe)
// => Int
}
val request: Request[Int] = createRequest(44)
getType(request.value)
共変と一緒に使う場合
Response[+T]と型パラメータに共変を指定している。
createResponseメソッドの戻り値を、Response[Any]としているが、Response[Int]でもResponse[String]でもAnyのサブ型なら返すことができる。
しかし、コンパイル時にはAny型としか明示されていないので、getTypeメソッドの結果は、やはりAny型になる。
import scala.reflect.runtime.universe._
import scala.reflect._
case class Request[T](types: String, value: T)
case class Response[+T](value: T)
def createResponse[T](request: Request[T]): Response[Any]= {
Response(request.value)
}
def getType[T](value: T)(implicit tag: TypeTag[T]) = {
println(tag.tpe)
}
val responseInt = createResponse(
Request(types = "Int", value = 22)
)
val responseString = createResponse(
Request(types = "Int", value = "test")
)
getType(responseInt)
// => Playground.Response[Any]
getType(responseString)
// => Playground.Response[Any]
ちゃんと型を指定すればok
import scala.reflect.runtime.universe._
import scala.reflect._
case class Request[T](types: String, value: T)
case class Response[+T](value: T)
def createResponse[T](request: Request[T]): Response[T]= {
Response(request.value)
}
def getType[T](value: T)(implicit tag: TypeTag[T]) = {
println(tag.tpe)
}
val response1: Response[Int] = createResponse(
Request(types = "Int", value = 22)
)
val response2: Response[String] = createResponse(
Request(types = "Int", value = "test")
)
getType(response1)
// => Playground.Response[Int]
getType(response2)
// => Playground.Response[String]
まとめ
scalaの型に関してちょっとだけ理解した気がする。