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つの方法があります。

  1. TypeTag には、実行時のすべてのタイプ情報が含まれています。
  2. ClassTag は、型のランタイムクラスを取得しますが、型パラメーターについては通知しません。
  3. 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

リストアレイの作成の違いは何ですか? 配列バージョンでコンパイラが型パラメータTにClassTagを必要とするのはなぜですか? 簡単に言うと、これは、JVMでの配列の表現方法が原因であるということです。

前述したように、コンパイラは List [T] の型パラメータを消去し、それをListに変換します。 したがって、型消去により、実行時に同じクラスを持つようになります。

一方、配列は、JVM内で異なるクラスを持っています。 コンパイラの型消去は、配列の型パラメータを消去せず、Array[String]とArray[Int]を区別します。 したがって、実行時に異なるクラスがあります。 それらをインスタンス化するには、実行時に配列の具体的なタイプが必要です。 これが、実行時にタイプパラメータTを保持するためにClassTagが必要な理由です。

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

今まで、 すべてのタイプが具体的です。 タイプが抽象の場合、TypeTagは使用できません。 これが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 、 と WeakTypeTag。 

いつものように、完全なソースコードはGitHubにあります。