Posted at

Macro Pardise: annottee と expandee

More than 3 years have passed since last update.

以前の記事で Macro Paradise の提供するマクロアノテーション機能について紹介しました。マクロアノテーションは、アノテーションで修飾された定義文 (annottee) を展開対象 (expandee) としたマクロ展開を行うことができる機能です。基本的に annottee = expandee なのですが、一部一致しない場合が存在します。これを利用して delegation を実現したマクロ scala-macro-aop を例に解説したいと思います。なお、このマクロは Scala 2.11 では動かないので修正したバージョンも用意しておきました。ちなみにこのマクロは名前とは違いAOPとは特に関係ありません。


Delegation

それでは、scala-macro-aop による delegation について解説します。ちなみに、口を酸っぱくして繰り返しますが、AOPとは特に関係がありません。

このライブラリでは、クラスのパラメータに @delegate アノテーションを付加するとそのパラメータとなったクラスのメソッドが生成されます。以下は公式の例から持ってきたものです。

trait Foo {

def method1(): String
def method2(p1: String): Long
def method3(p1: String): Int
def method4(p1: String, p2: Long): String
}

class FooWrapper(@delegate wrapped: Foo) extends Foo {
def method2(p1: String) = 41L
}

この例では、FooWrapperクラスのパラメータwrapped: Foo@delegateがついています。これによって、FooWrapperクラスにはFooクラスのメソッドmethod1,method3,method4が自動で追加されます。なお、このとき元々存在するmethod2は上書きされず、追加されません。

ここで @delegate はマクロアノテーションです。クラスのパラメータは内部的に val wrapped: Foo という変数定義の一種として扱われるため、ここにアノテーションを置くことができます。

ところで、この delegation は以前説明した annottee = expandee という単純な図式では実現できません。この例では、annottee は wrapped: Foo という極めて狭い範囲にすぎませんが、メソッドを追加するには expandee をより広く取る必要があります。


Macro Paradise での解決

この範囲が狭すぎる問題を Macro Paradise では、パラメータのときは expandee を広げることで解決しています。より具体的には、クラス・トレイト・メソッド定義のパラメータ・型パラメータが annottee のときは、expandee を外側のクラス・トレイト・メソッド定義としています。

では、マクロ定義を見てみましょう。以下のソースコードはレポジトリから抜粋したものです。


delegateMacro.scala

  def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {

// (略)
val inputs = annottees.map(_.tree).toList
val (_, expandees) = inputs match {
case (param: ValDef) :: (enclosing: ClassDef) :: rest => {
val newEnclosing = addDelegateMethods(param, enclosing)
(param, newEnclosing :: rest)
}
// (略)
}
// (略)
}

今回のように annotte と expandee が異なる場合には、annottee と expandee がそれぞれ別々に渡されます。具体的には、リスト annottees の1番目が annottee (wrapped: Foo)、2番目が expandee (class WrappedFoo 全体) です。ちなみに、クラスパラメータの場合にはコンパニオンオブジェクトも expandee に含まれるので、3番目にはコンパニオンオブジェクトが入ります。


長所・短所

この Macro Paradise のように expandee を広げるアプローチですが、一長一短あります。長所としては、表現力が上がり、今回のようにアプリケーションを増やすことができます。一方、短所としては、広げれば広げるほどその分だけ書き換えが面倒くさくなります。極端なことを言えば、プログラム全体ぽんと渡されて必要なところだけ書き換えろと言われても、困ってしまいます。それはコンパイラを作るくらい面倒なことで、これではマクロとはいったい何だったのでしょうか。何でもできるくらい表現力が高いというのは裏返せば、ユーザーにすごく努力させなければ何もできないということと同じということですね。


終わりに

今回は Macro Paradise の細かい機能について紹介しました。Macro Paradise は今後 scala.meta ベースになるなど、今回の話がいつまでもそのまま使えるかというとそれは難しいです。しかしながら、そのアプローチは未来の Scala だけでなく、ほかの言語でも活かしていけるのではないかと思います。