Scala 2.10のマクロを試してみた

Scala 2.10の新機能をあれこれと試しているのですが、中でも要注目のマクロを試してみました。とはいうものの、マクロは2.10ではまだexperimentalとして入るようで、使用するには明示的にインポート文を記述する必要があります(コンパイル時のオプション指定でも可)。

// マクロを使うにはこのインポート文が必要
import language.experimental.macros

話はそれますが、Scala 2.10では暗黙の型変換や後置演算子、部分的構造型を使用したメソッド呼び出しなどがデフォルトではコンパイル時に警告になるようです。それぞれ理由はあるようですが、すでにかなり広く使われている機能をいきなり警告にするとか非常にアグレッシブでいい感じですね。
ちなみに現在リリースされているScala 2.10 M2以降にマクロの書き方もだいぶ変わっているようです。この他にもM2が出た後で様々な新機能が入っているようなので、Scala 2.10の新機能を試すのであればナイトリービルドがオススメです。

んで、肝心のマクロはこんな感じ。しょぼいですが、マクロの展開がコンパイル時に行われていることがわかるよう、コンパイル時に取得したタイムスタンプを表示するようにしてみました。

import language.experimental.macros
import scala.reflect.makro.Context

object MacroSample {

  def compiledTime(): String = macro compiledTime_impl
  
  def compiledTime_impl(c: Context)(): c.Expr[String] = {
    import c.mirror._
    import c.reify
    // このタイムスタンプはコンパイル時に取得
    val date = Literal(Constant(new java.util.Date().toString))
    reify("Compiled Time: " + date.eval)
  }
 
}

このマクロを以下のような感じで呼び出すと、一回目も二回目も同じタイムスタンプが表示されます。

println(MacroSample.compiledTime)
Thread.sleep(1000)
println(MacroSample.compiledTime)

なお、マクロとマクロを使用するコードはコンパイル単位を分けておく必要があります。これはマクロの展開を

  1. まずマクロをコンパイル
  2. コンパイルされたマクロと一緒にマクロを使うコードをコンパイル

という流れで行っているからのようです。
また、マクロの実装は展開する式のツリーを返す必要があるわけですが、

Literal(Constant(new java.util.Date().toString))

の部分のように気合でツリーを組み立てる方法と、

reify("Compiled Time: " + date.eval)

のようにreifyを使って普通に記述したScalaコードをツリーに変換して返す方法があります。reifyはLispでいうところのバッククォートみたいなものと考えて差し支えないかと思いますが、もともとS式のLispと比べると有り難味が段違いです。展開されるコードがイメージしやすくなるので、よほど簡単なマクロ以外はreifyを使うことになるでしょう。