Scalaマクロ Tips: Typeの取得 2013年10月16日

今回はマクロの中で型を取得する方法について解説したいと思います。型全般だと書くことが散漫になりそうな感じだったので、取得方法に絞ることにしました。

ここで型と言っているのはTypeオブジェクトのことです。 Typeは型の名前や、どんなフィールド、メソッドを持っているかなど色々な型情報を持っているオブジェクトです。 リフレクションでもTypeを起点にフィールドやメソッドのSymbolを操作するような感じで中心的な役割を担っています。

Typeを取得するにはTypeTagを使うのが一般的です。TypeやTypeTagについては公式のドキュメントもあります。

["reflection"] - シンボル、構文木、型 - Scala Documentation

["reflection"] - 型タグとマニフェスト - Scala Documentation

Typeの用途は以下のようなものが考えられます。

  1. 型の情報を取る
  2. 型同士の比較をする
  3. 型の構文木を使う

具体的な用途についてはまた別の機会に解説しますが、特に型変数を使うようなマクロでは重要になると思います。

IntやListなどScalaで定義されている型はtypeOf[Int]typeOf[List[String]]のような感じで簡単にTypeオブジェクトを取得することができますが、型変数を使った場合はWeakTypeTagを使うか、型つけされた構文木からTypeを取得する必要があります。

WeakTypeTagを使う

TypeTag、WeakTypeTagというのはTypeが入っているだけのコンテナです。Typeを取得するためだけに使われます。 TypeTagとWeakTypeTagの違いはTypeTagは具象型に限定されるのに対し、WeakTypeTagは抽象型も入っていることがあります。 マクロで使うのはWeakTypeTagだけになります。 例を見てみますと、

import scala.reflect.macros.Context

object WTypeTag {
  def wtypetag[T](t: T) = macro impl[T]

  def impl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]) = {
    import c.universe._
    c.literal(weakTypeOf[T].toString)
  }
}
object Main extends App {
  def f[T](t: T) = WTypeTag.wtypetag(t)
  println(WTypeTag.wtypetag(123))
  println(f(123))
 }

weakTypeOf[T]というのはimplicitly[WeakTypeTag[T]].tpeと同じです。WeakTypeTagオブジェクトはコンパイラが自動で生成してくれるので、implicit parameterにするだけで取得することができます。

注意すべきところは上で述べたようにTypeTagは具体的な型情報が入っていることが保証されていますが、 WeakTypeTagはそうではないというところです。上のコードを実行すると、

Int
T

と表示されます。直接マクロにIntリテラルを渡した場合は具体的なInt型を取得できていますが、 型変数を持つメソッドを経由すると型情報が失われています。 マクロがコンパイル時に展開されることを考えれば当然なんですが、 型の情報を調べようと思うと落とし穴になるかもしれないので気をつけてください。

型つけされた構文木から型を取得する

型つけされた構文木からもTypeオブジェクトを取得することができます。defマクロの引数は型つけされているので直接Typeを取得することができます。

import scala.reflect.macros.Context

object TypeFromTree {
  def typeFromTree[T](t: T) = macro impl[T]

  def impl[T](c: Context)(t: c.Expr[T]) =
    c.literal(t.actualType.toString)

  def intListTypeImpl(c: Context) = {
    import c.universe._
    c.literal(c.typeCheck(q"List(1, 2, 3)").tpe.toString)
  }
}
object Main extends App {
  def g[T](t: T) = TypeFromTree.typeFromTree(t)

  println(TypeFromTree.typeFromTree(123))
  println(g(123))

  println(TypeFromTree.intListType)
}

実行すると以下のような結果になります。

Int(123)
T
List[Int]

t.actualTypeで型を取得できます。これはt.tree.tpeと同じです。 型変数に対して具体的な型を取れないのはWeakTypeTagと同じです。 ちなみにt.staticTypeというのもありますが、これはweakTypeOf[T]と同じです。 actualTypeはリテラルに使うとWeakTypeTagより詳細な型を取れますが、基本的にそんなに違いはないと思います。

ちょっと変わった型の取得方法としては構文木を自分で型チェックして取得する方法があります。 c.typeCheck(q"List(1, 2, 3)").tpeの部分がそうですね。 あまり使わないと思いますが型つけされた構文木からは型が取得できると覚えておけばいいと思います。

今回はTypeオブジェクトの取得方法について解説しました。このあたりはScala 2.10以降で大きく変わったところで、取得できる情報が非常に増えています。 リフレクションがスレッドセーフではないということでちょっと使いづらいのですが、マクロはそういう問題はないので、活用していきましょう。