Scala.meta 1.0.0 記念: Macro Paradise 3.0.0-M{1,2,3} 紹介


Scala.meta 1.0.0 がリリースされたみたいですね！それはさておきこの記事では、最近登場した[Macro Paradise 3.0.0-M{1,2,3}](https://github.com/scalameta/paradise)の紹介します。

Macro Paradise 3.0.0-M{1,2,3} は SBT で以下のように書くことで使えます。

```scala
addCompilerPlugin("org.scalamacros" % "paradise" % "3.0.0-M3" cross CrossVersion.full)
```

## Scala meta

Scala meta は、Scala コードを解析したり、自動生成したりする人のためのフレームワークです。アプリケーションとしてコードフォマッターの [Scalafmt](https://olafurpg.github.io/scalafmt/) や、静的解析器の [Codacy](https://codacy.com/) などがあるそうです。最終的には、コンパイラやマクロなどもサポートする予定のようですが、1.0.0 では Scala の構文木を表現するためのクラス群のみが提供されています。今のところは Scala meta に対応したマクロを提供するプラグインなどはなさそうです。(あったら教えて下さいな ^^)

## Macro Paradise 3.0.0-M3

Macro Paradise 3.0.0-M3 では Scala meta で提案されている新しいスタイルのマクロを提供する…と思いきや、しません。Macro Paradise の機能は相変わらずマクロアノテーションのみです。では何が新しいかというと、まだ 1.0.0 には入っていない新しい Scala meta 形式でマクロアノテーションが作れる点です。

以下の例は、公式にある例で、 object に `main` メソッドを追加します。

```scala
import scala.meta._

class main extends scala.annotation.StaticAnnotation {
  inline def apply(defn: Any) = meta {
    val q"object $name { ..$stats }" = defn
    val main = q"""
      def main(args: Array[String]): Unit = { ..$stats }
    """
    q"object $name { $main }"
  }
}
```

以下に示す 2.1.0 風と比較すると幾つかの点が異なります。

```scala
class main extends scala.annotation.StaticAnnotation {
  def macroTransform(annottee: Any*): Any = macro Bundle.impl
}
import scala.reflect.macro.whitebox.Context
class Bundle(c: Context) {
  import c.universe._
  def impl(annottee: Tree*): Tree = { ... }
}
```

まず、マクロメソッドがインターフェースを担う `macroTransform` と変換メソッド (ここでは `impl`) に分かれていたのが `apply` 1つに集約されました。また、`inline`と`meta`の2つのキーワードが導入されました。逆に言うと、この `inline` を使って定義された `apply` メソッドをもつ `StaticAnnotation` のサブクラスがプラグインにマクロアノテーションと認識されます。

仕組みを説明しましょう。構文解析フェーズで `inline` を検出すると以下のように機械的に書き換えます。

```scala
class main extends scala.annotation.StaticAnnotation {
  @scala.meta.internal.inline.inline def apply(defn: Any) = ???
}
object main$impl {
  def apply$impl(defn: metaTree) = {
    val q"object $name { ..$stats }" = defn
    val main = q"""
      def main(args: Array[String]): Unit = { ..$stats }
    """
    q"object $name { $main }"
  }
}
```

これによって、2.1.0 と同様の形式に直してしまうわけですね。

なお、`meta`は今のところは何の機能も果たしていませんが、将来的には必要な implicit を暗黙のうちに import させるブロックとして機能する予定のようです。ちなみに、厳密には`meta`はキーワードではなく、クロージャを取るメソッドという扱いのようですが、現行の Scala には暗黙の import をするブロックをサポートする機能が存在しないのでどうするつもりなのか興味があります。[^1]

[^1]: そのような機能を持つプロトタイプとして[Inverse macro の副産物としてできたプラグイン](https://github.com/hiroshi-cl/scala-examples/tree/master/scala-implicit-import)があるので今度紹介記事を書きたいと思います。

## M1/M2/M3 の違い

M1 では M2 以降と違い以下のように object 内に定義していました。

```scala
import scala.meta._

object main {
  inline def apply()(defn: Any) = meta {
    val q"object $name { ..$stats }" = defn
    val main = q"""
      def main(args: Array[String]): Unit = { ..$stats }
    """
    q"object $name { $main }"
  }
}
```

これまでのマクロでは object か macro bundle という特別な形式の class にしか定義することができなかったためだと思われます。しかし、よく知っている人はわかるように object では `@main` は invalid なアノテーションで、相当不自然です。そのせいか、M2 からは普通のアノテーションに置き換えられました。

この副作用として、アノテーションの引数をマクロ内で直接取れるようになりました。引数というのは `@compileTimeOnly("これはコンパイル時限定")` のようなものの `"これはコンパイル時限定"` の部分のことです。2.1.0 では、`c.prefix` を呼ぶとアノテーション全体分の構文木が取れるので、それを自分でバラす必要がありました。割と面倒でした。

M2 では、今回紹介したようなマクロアノテーションに仕様変更が行われました。

M3 では、`def`や`val`のようにトップレベルでないものにもアノテーションが付けられるようになりました。実は M2 ではトップレベルしか対応していなかったのですね。はい。よく見ると[issue#3](https://github.com/scalameta/paradise/issues/3)にも書かれていました。実はこのバージョンアップは私の犯行です。[前回の記事](http://qiita.com/hiroshi-cl/items/0ee4bf494779f62e2373)で新バージョンがあるという事実を見逃したリベンジをしようとしてできなかったので、ついカッとしてプルリクを投げてしまいました。反省はしています。

## Scala meta ではまったところ

- `Param` は `Term` じゃないので `Arg` にならない
  - `Defn.Def`のパラメータを使ってメソッド適用の木を作るのがめんどい
- `Defn` (メンバ？) と `Decl` (ローカル？) が違う
  - `Defn` は `Block` に入れられないので 2.1.0 の複数返すテクが使えない…
- 依存ライブラリに指定されている 0.20.0 では `...` 記法がバグってた
  - 1.0.0 では直っているらしいけどまだ確認できていない