Implicit Macro を利用した名前呼び引数と合成可能なマクロの作り方

# Introduction

Scala 2.10 以降では型付きの構文マクロ機能があり、これを用いて特殊な機能をもたせた『ブロック』を提供するライブラリがいくつかあります。例えば、async/await 演算子による非同期並列化を提供する Scala Async[^async] や、モナディックなライブラリに対して for 内包表記よりも簡潔な表記を提供する effectful[^eff] などがあります。

これらのマクロライブラリは有用なのですが、名前呼び引数のような Scala に元々存在する『ブロック』を提供する言語機構とは合成できないという問題があります。この記事では、この問題について解説するとともに implicit macro という機能を用いた解決法を紹介したいと思います。

[^async]: https://github.com/scala/async
[^eff]: https://github.com/pelotom/effectful

# Motivating Example

マクロと名前呼び引数が合成できないという問題を Scala Async を用いて説明したいと思います。

Scala Async は最近各種言語で流行りの async/await を用いた非同期並列化をマクロを用いて簡潔に書けるEDSLを提供するライブラリです。これを使うと`async {...}` というマクロの中で `await` というキーワードを使って簡単に非同期のジョインを書くことができます。例えばフィボナッチ数をFutureで書くと以下のようになりますが、

```scala:future
def fib(n: Int): Future[Int] =
  if (n <= 1)
    Future { 1 }
  else {
    val f1 = fib(n - 1)
    val f2 = fib(n - 2)
    for (x1 <- f1; x2 <- f2) yield x1 + x2
  }
```

Scala Async で書くと以下のようにスッキリさせつつも上と同じような並列化を実現することができます。[^1]

[^1]: 厳密にいうと Scala Async はコルーチンベースのコードを出力するためFuture版よりかなり複雑なコードを吐きます。

```scala:async
def fib(n: Int): Future[Int] =
  if (n <= 1)
    async { 1 }
  else async {
    val f1 = fib(n - 1)
    val f2 = fib(n - 2)
    await(f1) + await(f2)
  }
```

それではこの Scala Async を使って別の EDSL "Scala Sync" を作ることを考えましょう。この sync は async と同様に中で `await` を使うことができますが、全体を `Await.result(_, duration.Duration.Inf)` で囲んだような効果があるものとします。 [^2]

[^2]: 例のために作った無理矢理な例なので実用性ほぼゼロですが、一応[以前書いた記事](http://qiita.com/hiroshi-cl/items/efde7af2b184dbd8cfb2#%E3%81%AA%E3%81%9C-awaitready-%E3%81%8C%E5%BF%85%E8%A6%81%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%A6%E3%81%97%E3%81%BE%E3%81%86%E3%81%8B)のような使い所があったりします。

さて、どうやって作るとよいでしょうか。愚直な方法としては "Scala Sync" もマクロで作ってしまうことが考えられます。しかし、マクロは扱いがとても難しく、それを要求するととても敷居が高くなってしまうので、できれば直接触るのは避けたいです。

もう1つの方法としては、今回問題としている名前呼び引数を使うというものが考えられます。イメージとしては以下のようになります。

```scala:syncのイメージ
def sync[T](body: => T): T =
  Await.result(async { body }, duration.Duration.Inf)

sync {
  // この辺で await が使えるとイイな
}
```

少し考えればわかるようにこれはうまくいきません。`sync`を定義した人は引数の構文木を`async`に渡してうまく変換してくれることを期待しています。しかしながら、`sync`はただの名前呼び引数を持つメソッドであり、このメソッド呼び出しを通した時点で構文木の情報が消えてしまいます。実際、上のコードの場合、`async`に渡されるのは`body`という変数を示すノードだけからなる構文木です。このようにマクロと名前呼び引数を愚直に合成しても思ったように動作させることができません。

# Implicit Macro の利用

この記事では implicit macro に基づいた別の連携可能なマクロを定義することで、名前呼び引数と連携可能にする方法を紹介します。Implicit macro とは、Scala に存在する implicit parameter 機能と連携して展開する特殊なマクロです。かなりマイナーな機能ですが、有名ライブラリの shapeless[^shape-git] で使われていたり、[^shape] Scala の variant の Scala virtualized で提供される SourceLocation 機能のエミュレート[^sip-19]に使われていたりするようです。

[^shape-git]: https://github.com/milessabin/shapeless
[^shape]: http://docs.scala-lang.org/ja/overviews/macros/implicits.html
[^sip-19]: http://docs.scala-lang.org/sips/pending/source-locations.html, https://github.com/scala/scala/tree/2.11.x/test/files/run/macro-sip19-revised

さて、この implicit macro ですが、あるメソッド呼び出しにおいて implicit parameter として渡されたとき、そのメソッド呼び出し全体に相当する構文木を参照できるという機能があります。この機能を利用すると `async` のように引数の構文木を直接マクロで変換する代わりに、同じメソッド呼び出し中に含まれる、いわば"隣の"名前呼び引数の構文木を書き換えてその結果を渡すという離れ業が可能になります。これにより、既存のマクロを直接変更なしに利用できるとまではいきませんが、簡単な修正により implicit macro 化することで様々な名前呼び引数のメソッドで組み合わせて利用できるようになります。

では`sync`を implicit macro を用いて実装する例を示します。コード例は以下です。このコード例では`sync`メソッドに `implicit a: Future[T]`という implicit parameter を追加しました。これにより、`sync`を使用したスコープにおける `Future[T]`型の implicit value として `implicitAsync[T]` が推論されるようになります。この `implicitAsync[T]` が implicit macro となり、展開時に `body` に渡された式の構文木を `async` と同様に変換したものに置き換えられます。変換メソッドの本体は `implicitAsyncImpl` に定義されますが、ここでは省略させていただきます。

```scala:sync_implicit_macro
def implicitAsyncImpl(c: whitebox.Context): Tree = { (略) }

implicit def implicitAsync[T]: Future[T] = macro implicitAsyncImpl

// implicit parameter を取る
def sync[T](body: => T)(implicit a: Future[T]): T =
  Await.result(a, Duration.Inf)
  
def main(args: Array[String]) {
  val result: String =
    sync {
      val fib40:  Future[Int] = fib(40)
      val trib40: Future[Int] = trib(30)
      "fib(40) = " + await(fib40) + ", trib(30) = " + await(trib30)
    }//(implicitAsync[String]) が暗黙的に渡される
  println(result)
}
```


さて、implicit macro は Scala で提供されている普通のマクロとどう違うのでしょうか。普通のマクロは def macro と呼ばれています。普通のメソッドのように def キーワードを用いて定義され、メソッド呼び出しであるかのように使われ、そして、使われた場所でメソッド呼び出しの引数に相当する型付き構文木を切り取って処理します。Implicit macro も実はほぼ def macro と同じで、先頭に `implicit` キーワードが付いていて implicit value として補完されるくらいしか形態的には違いがありません。

ただ、`Context` に [`whitebox.Context`](http://www.scala-lang.org/api/2.11.8/scala-reflect/index.html#scala.reflect.macros.whitebox.Context) を選択すると、`enclosingImplicits` と `openImplicits` という2つの implicit macro のときだけ機能する特別な API を使用することができます。これが前述のあるメソッド呼び出しで implicit value として補完されたときに、そのメソッド呼び出しの構文木を取ってくる機能です。[^list]

[^list]: 余談ですが、このメソッドはリストを返します。なぜなら、implicit def では implicit な parameter を設定することで、多段補完が実現することができ、実はマクロに関係する implicit value は1個だけではないからです。リストの前から、内側から外側への順に並んでいます。今回のマクロでは基本的に1番外側、つまり1番後ろの候補を利用することになります。

## 実行可能なコード例

読みやすいコード例は用意していません。すいません。

代わりにと言ってはなんですが、2014年の6月頃に書いたらしいコードを晒したいと思います。1つは、byname macro[^byname] というもので apply method がマクロになっているという一定の呼び出し規約に沿ったマクロに対して implicit macro 版を提供できるものです。もう1つは、byname macro を使って限定継続演算子 shift/reset の reset 演算子を実装してみたものです。Scala には continuation plugin[^cps] がありますが、これはマクロのみで実装するというコンセプトを検証するために作ったものです。 なお、implicit conversion を乱用していることにより if/match/try など LUB が計算される箇所で Any が推論されて落ちる、メソッドをまたいだ継続を扱えるもののメソッドごとに `reify` で囲まなければならない、などの問題があり実用性はありません。

[^byname]: https://github.com/hiroshi-cl/scala-misc/tree/master/byname_macro/src
[^reset]: https://github.com/hiroshi-cl/scala-misc/blob/master/cps_macro/src/main/scala/jp/ac/u_tokyo/i/ci/csg/hiroshi_yamaguchi/macros/monad/ShiftMonad.scala
[^cps]: https://github.com/scala/scala-continuations

## 注意点

この手法を使用する上での注意点を幾つか列挙しておきます。ただし、筆者が使用していたのは主に1年以上前で忘れてしまったため、思い出したら何か書き足します。

### パフォーマンス上の問題

この記事で紹介した方法では、実行時には使用されないクロージャが作成されるという問題があります。より詳しくいうと名前呼び引数の構文木がそのまま残るため、これが作ったが使われない構文木として残る結果となります。`@inline` アノテーションと`-optimise`オプションを利用してインライン展開するといくらか解決されますが、パフォーマンスがクリティカルなアプリケーションに使用するときは注意すると良いでしょう。

また、パフォーマンスの問題ではありませんが、これに関係して実は "sync" は今回紹介した方法では作ることができません。これは`await`が`@compileTimeOnly`付きで定義されているためで、今回のように使われないとはいえ構文木が書き換えられずにそのまま残ってしまうとコンパイルエラーになってしまうからです。とてもつらい…

### Whitebox

Scala 2.11 の macro は、比較安全な blackbox macro といくらか危ない機能を含んだ whitebox macro の2種類に分かれています。[^wb] この記事で紹介した `enclosingImplicits` は、whitebox の方でのみ使用可能な機能です。ちなみに、Scala 2.10 にあった周囲の構文木に読むことができる enclosing 系のAPIが 2.11 で軒並み消された中、 `enclosingImplicits` だけは whitebox macro として残されています。かろうじて生き残っているとはいえ、主流からは外れてしまったので、今後消されてしまう可能性もあります。また、消されなかった場合でも盛んに開発されている scala.meta 系の新しいマクロには入らないという可能性も十分にありえます。

[^wb]: http://docs.scala-lang.org/ja/overviews/macros/blackbox-whitebox.html

### 多段になったときがあまり直感的じゃないかも

今回はマクロを直接使って作る "sync" だけでしたが、この "sync" を利用してさらに別の DSL を作りたいというのは十分考えられます。こういったとき、少し工夫が必要です。(…説明を書くのに面倒になってしまったので読者への課題とします。

Macro を直接触らずに DSL を開発できるようになったとはいえ、implicit parameter を毎回設定しないといけないため、この点もあまりスマートとはいえないかもしれませんね。

# 発展: Inverse Macros

ところで、このテクの欠点を踏まえ、名前呼び引数に限らず、より多くの応用例に関してメソッドと組み合わせ可能で、よりスマートに記述できるマクロ機構として inverse macro というものを開発することになりました。[^im] 2.12 版は現在調整中ですが、2.11 版は利用することができます。よろしければこちらも試しに使ってみてください。

なお、inverse macro は純 Scala ではなく、compiler plugin による拡張が必要です。純 Scala でなければならないアプリケーションに対しては、今回紹介した方法を用いると良いでしょう。

[^im]: http://dl.acm.org/citation.cfm?id=2814213, https://github.com/hiroshi-cl/InverseFramework