Scala 2.10 Reflection and Macros


@pab_tech

内容


  • Scala 2.9までのリフレクションの状況を軽く説明
  • Scala 2.10のリフレクションの紹介
  • Scala 2.10のマクロの紹介

Scala 2.9までのリフレクション


  • Manifestを使ってJavaのクラスを取得
    → Javaのリフレクションを使う。
  • 裏技的にScalaSignatureを読み込み情報を取得する。

要するにScalaとしてはリフレクションの手段をちゃんと用意していなかった。

ClassManifestとManifest


  • Implicit Parameterとして取得する。
  • Javaのクラスにアクセスできる。

def hoge[T: ClassManifest] = { 
  val c = implicitly[ClassManifest[T]].erasure
  ...
}

ClassManifestとManifest


  • 基本的にJavaのクラスを取得するためだけのものと考えてよい。
  • 型の比較もできる。が、あまり使わないと思う…。
  • JVMの型消去のため、型変数からクラスを取得できないために、こういう手段を使わざるをえない。
  • ClassManifestはそのクラスの情報だけ持っているだけ。
  • ManifestはClassManifestの機能に加えて、型引数のManifestも持っている。

ScalaSignature


  • Javaのクラスファイルに入っているScalaの情報。
  • ScalaSigParserを使って情報を取得することができる。

scala> import scala.tools.scalap.scalax.rules.scalasig._

scala> val scalaSig = ScalaSigParser.parse(classOf[List[_]]).get

scala> scalaSig.topLevelClasses(0).children foreach println

TypeSymbol(A, owner=0, flags=12100, info=19 )
MethodSymbol(<init>, owner=0, flags=200, info=47 ,None)
MethodSymbol(companion, owner=0, flags=220, info=50 ,None)
MethodSymbol(isEmpty, owner=0, flags=300, info=56 ,None)
MethodSymbol(head, owner=0, flags=300, info=62 ,None)
...

ScalaSignature


  • 型情報に加えてメソッドの情報などもわかる。
  • しかし公式にAPIが公開されているわけではなく、利用者も少ない。
  • xuwei_kさん曰く「積極的に使うべきものではない」とのこと。

Scala 2.10のリフレクション


Scala 2.9までと比べると大幅に整理、強化されている。


  1. 今までScalaSignatureで取得していたような情報は、TypeTagを使ってアクセスできる。
  2. メソッド呼び出しなどの操作は、Mirrorを使って行うことができる。
  3. 構文木を組み立てることで、任意のコードを実行することができる。

3番目はリフレクションとしては特徴的。構文木の作成はマクロと共通の機能になっている。

ClassTagとTypeTag


  • Scaladoc に詳しい説明がある
  • Manifestの置き換えらしい。
  • Implicit Parameterで取得するのは同じ。
  • ClassTagはほとんどClassManifestと同じ。つまりJavaのクラスを取得するだけ。
  • TypeTagはもはやManifestとは別物と思われる。

TypeTagとType

  • TypeTagはTypeオブジェクトを取得できる。
  • TypeオブジェクトはScalaSignatureで取得できたような情報が入っている。
  • implicitかどうかなど、Javaのリフレクションでは取得できなかった情報も取得できる。

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> val t = typeTag[List[_]].tpe
t: reflect.runtime.universe.Type = scala.List[_]

scala> val m = t.member(newTermName("filter"))
m: reflect.runtime.universe.Symbol = method filter

scala> m.asMethod.isImplicit
res0: Boolean = false

Universe


  • CAKEパターンで作られたリフレクションAPIの集合体。
  • 細かく階層が分かれている。
  • ただのクラス設計かと思いきや、どの階層でコードを書いているかを意識することが重要。

Universe

Reflection universes

Universe


  • マクロはreflect.macro.Universeを使う。
  • リフレクションはreflect.api.JavaUniverseを使う。
  • 両方から使えるものを作りたい場合はreflect.api.Universeを使う。
  • ちなみにGlobalというのはコンパイラで使われるもので、コンパイラプラグインを作るときに出てくる。
  • マクロではContextでもAPIが提供されており、階層を行ったり来たりすると、どこで何が定義されているのか、何が使えるのかで頭がこんがらがる。

Mirror


  • Symbolはクラスやメソッド、変数などの定義に対応している。
  • Symbolが表わしている対象をメタレベルで操作するときにはMirrorを使う
  • トップレベルのMirrorはJavaのクラスローダに対応おり、そこからオブジェクトのMirrorやメソッドのMirrorを作る。
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> import scala.reflect.runtime.{currentMirror => cm}
import scala.reflect.runtime.{currentMirror=>cm}

scala> val m = typeOf[List[_]].member(newTermName("head"))
m: reflect.runtime.universe.Symbol = method head

scala> cm.reflect(List(1,2,3)).reflectMethod(m.asMethod)()
res0: Any = 1

マクロ


  • 今のところ予定のうちの一部の機能しか実装されていない。
  • 使えるのは「型付きの関数のマクロ」だけ。
  • 「型付きの関数のマクロ」というのは型付けされたScalaの式を受け取り式を返すマクロ。
  • この制約がきつく、新しい構文の導入やコードの生成といったものがやりにくいことになっている。

リフレクションとマクロの比較

マクロの特徴


  • 型付けされたScalaのコードの構文木を受け取ることができる。
  • そのマクロを呼び出した構文やクラスの情報にアクセスできる。
  • コンパイル時に計算できるので実行時の処理が軽くて済む。
  • 型チェックできる。
  • あまり大きな式を作ってしまうと展開されたときにコードのサイズが大きくなる。

リフレクションの特徴


  • 動的に動作を変えることができる。
  • 実行時に構文木を解釈することになるので、処理が重くなる。
  • 型チェックができない。
  • 場合によっては広く展開されてしまうマクロよりコードサイズが小さくなる。