Scala 2.11から導入されるBlackboxMacroとWhiteboxMacro 2013年12月16日

Scala Advent Calendar 2013 - Qiita [キータ]の16日目の記事です。

Scala 2.11から導入されるBlackboxMacroとWhiteboxMacroについて簡単に解説します。

最近出た2.11.0-M7でscala.reflect.macros.ContextがDeprecatedになり、 scala.reflect.macros.BlackboxContextscala.reflect.macros.WhiteboxContextが増えました。

詳しくは以下のドキュメントに書いてあります。

["macros"] - Blackbox Vs Whitebox - Scala Documentation

現在のマクロの型チェックは結構怪しい動作をしているという話で、 そのお陰でImplicitマクロやType Providerもどきのようなテクニックがあるんですが、動作が直感に反してることもあります。

Static return type of Scala macros - Stack Overflow

このStack Overflowの質問はマクロで返値の型指定ができないという話です。

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

class Foo
class Bar extends Foo { def launchMissiles = "launching" }

object FooExample {
  def foo: Foo = macro foo_impl
  def foo_impl(c: Context): c.Expr[Foo] =
    c.Expr[Foo](c.universe.reify(new Bar).tree)
}

このマクロで返値の型をFooと指定しているのに

scala> FooExample.foo
res0: Bar = Bar@4118f8dd

実際に呼び出すと型がBarになってしまうということです。 マクロの型の指定はあくまでマクロ展開時の型チェックであり、展開後の型には反映されないんですね。

こういう問題は新しく導入されたBlackboxマクロを使うと解消できます。 マクロの型指定がマクロ展開後の型に影響するようです。 Scala 2.11.0-M7以降で以下のようなコードに変更すると、

import language.experimental.macros
import scala.reflect.macros.BlackboxContext

class Foo
class Bar extends Foo { def launchMissiles = "launching" }

object FooExample {
  def foo: Foo = macro foo_impl
  def foo_impl(c: BlackboxContext): c.Expr[Foo] =
    c.Expr[Foo](c.universe.reify(new Bar).tree)
}

ちゃんと型がFooになります。

scala> FooExample.foo
res0: Foo = Bar@c2030ed

しかし、同時にBlackboxマクロにすると今のScalaのマクロで許されている怪しいテクニックも使えなくなります。

Whiteboxマクロは2.10のマクロと同じ動作になります。互換性を考えるとこちらを使いたくなりますが、 現在の予定ではScala 2.12でBlackboxマクロだけになり、Whiteboxマクロ=現行のマクロの動作はとりあえずなくなるようです。

TypeマクロやUntypedマクロの提供をやめた今までのマクロの方針から考えると納得ではあります。 しかし、逆に言うと今のマクロのテクニックは型チェックしないことで許されてるものが多いということもわかりました。 個人的にはマクロはもうちょっと型がゆるくてもいい気がするんですけどねえ。