Scalaで実行時に型情報を保持する
1. 概要
Scalaでは、コンパイラーはコンパイル時にすべての汎用型情報を削除し、実行時にこの情報を失わせます。 TypeTagと、それに対応するClassTagやWeakTypeTagなどは、実行時に消去された型の型情報を取得するためのソリューションです。
このチュートリアルでは、型消去と、実行時に型の詳細を取得する方法について説明します。
2. 型消去
実行時に、すべてのタイプパラメータが削除されました。 したがって、List[Int]とList[String]は同じ。と見なされます。
List[Int]とList[String]を定義しましょう。
val intList: List[Int] = List(1, 2, 3)
val strList: List[String] = List("foo", "bar")
パラメータのタイプをチェックする関数も定義しましょう。
def checkType[A](xs: List[A]) = xs match {
case _: List[String] => "List of Strings"
case _: List[Int] => "List of Ints"
}
この場合、前述のように、Listの型とListの型パラメーターが消去されるため、コンパイラーは上記の関数の型消去について警告します。 List[Int]またはList[String]パラメーターを指定してcheckType()を呼び出しても、結果は同じになります。
コンパイラは、パターンマッチングの型パターンがチェックされていないことを警告します。
On line 2: warning: non-variable type argument String in type pattern List[String] (the underlying of List[String]) is unchecked since it is eliminated by erasure
case _: List[Int] => "List of Ints"
^
On line 3: warning: 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] => "List of Ints"
この場合、 checkType メソッドは、最初のケース値 case _:List [String] :を返します。
assert(checkType(intList) == "List of Strings")
assert(checkType(strList) == "List of Strings")
次に、このような問題を克服する方法について説明します。
3. 実行時型情報
では、実行時に変数の型情報を取得するにはどうすればよいでしょうか。 Scalaコンパイラには、実行時に型情報を生成する3つの方法があります。
- TypeTag には、実行時のすべてのタイプ情報が含まれています。
- ClassTag は、型のランタイムクラスを取得しますが、型パラメーターについては通知しません。
- WeakTypeTag は、抽象型情報を取得するTypeTagのより弱いバージョンです。
これらの各方法を見てみましょう。
3.1. TypeTag
TypeTag は、実行時に型情報を取得するための最も強力で詳細な方法です。 TypeTag [T]のimplicitを見つけるようにコンパイラーに指示すると、タイプ情報を簡単に取得できます。
def obtainTypeTag[T](implicit tt: TypeTag[T]) = tt
これで、任意のタイプでこの関数を呼び出すことにより、実行時に implicit TypeTagを見つけることができます。
assert(obtainTypeTag[Int].tpe == Int)
または、ネストされたタイプから情報を取得できます。
obtainTypeTag[List[Map[Int, (Long, String)]]]
さらに、scala.reflect.runtime.universeパッケージのtypeTagメソッドを呼び出して、 TypeTag[T]を取得できます。
TypeTag[T]にはtpeメソッドがあり、にはタイプTの反射表現が含まれています。 typeOf [T] は、 typeTag [T].tpeのショートカットメソッドです。
checkType メソッドを更新して、TypeTagを使用してみましょう。
def checkType[T: TypeTag](v: T) = typeOf[T] match {
case t if t =:= typeOf[List[String]] => "List of Strings"
case t if t =:= typeOf[List[Int]] => "List of Ints"
}
assert(checkType(List("foo", "bar") == "List of Strings")
assert(checkType(Seq(1, 2, 3)) == "List of Ints")
タイプを取得したら、 =:= 、 <:< 、 と weak _ <:< そのタイプに関する詳細情報を判別するための述語。
3.2. ClassTag
同じタイプの要素のListを作成する関数を作成するとします。
def makeListFrom[T](elems: T*): List[T] = List[T](elems: _*)
makeListFrom(1、2、3)関数を呼び出すことにより、任意の要素からListをインスタンス化できます。
要素のリストから配列を作成するのはどうですか? 同じように書くことができるようです:
def makeArrayFrom[T](elems: T*): Array[T] = Array[T](elems: _*)
ただし、この場合、エラーが発生します。
No ClassTag available for T
def makeArrayFrom[T](elems: T*): Array[T] = Array[T](elems: _*)
^
Compilation Failed
リストとアレイの作成の違いは何ですか?
前述したように、コンパイラは List [T] の型パラメータを消去し、それをListに変換します。 したがって、型消去により、実行時に同じクラスを持つようになります。
ClassTagを型パラメーターのコンテキストバウンドとして使用するメソッドを定義しましょう。
def makeArrayFrom[T: ClassTag](elems: T*): Array[T] = Array[T](elems: _*)
これで、 makeArrayFrom()関数を呼び出すことができます。
assert(makeArrayFrom(1, 2, 3) == Array(1, 2, 3))
3.3. WeakTypeTag
今まで、
メソッドの例を見てみましょう。
trait Foo {
type Bar
def barType = typeTag[Bar].tpe
}
特性Fooには抽象型Barがあります。型Barの反射表現を公開するbarTypeを記述しました。しかしこのコードコンパイラが抽象型Barの適切なTypeTagをインスタンス化できないため、コンパイルされません。
error: No TypeTag available for Foo.this.Bar
ここでWeakTypeTagが機能します。 WeakTypeTagを使用してbarTypeメソッドを書き直してみましょう。
trait Foo {
type Bar
def barType = weakTypeTag[Bar].tpe
}
これで、barTypeメソッドを呼び出すことができます。
assert(new Foo {
override type Bar = Int
}.barType.toString == "Foo.this.Bar")
Bar タイプは抽象であるため、コンパイラーはそのタイプの implicit WeakTypeTag を生成でき、実行時に使用可能になります。
4. 結論
この記事では、Scalaの型消去と、を使用して実行時に型情報を取得する方法について説明しました。 TypeTag 、 ClassTag 、 と
いつものように、完全なソースコードはGitHubのにあります。