Elixir の DSL がどう実装されているか、マクロの展開順を追ってみた（Plug.Builder を例に）

## はじめに

Elixir の [Plug](https://github.com/elixir-lang/plug) は、モジュールを組み合わせるために以下の DSL を提供している：

```ex
defmodule MyModule do
  use Plug.Builder
 
  plug :hello
  plug :world, good: :morning
end
```

`use Plug.Builder` したモジュール内で `plug :atom, [optional args]` と書くことで、モジュールに他の plug を組み込むことができる。

マクロを駆使して実装された `plug` の中身がどうなっているのか、 [Plug.Builder](https://github.com/elixir-lang/plug/blob/master/lib/plug/builder.ex) を再実装しながら追ってみることにした。

### マクロの参考資料

- [Elixir Docs - Kernel](http://elixir-lang.org/docs/stable/elixir/Kernel.html)
- [Getting Started - Macros](http://elixir-lang.org/getting-started/meta/macros.html)

## MyPlugBuilder の概要

Plug.Builder を丸ごと再実装するのは骨が折れるので、 `plug` マクロだけを提供するように単純化したモジュール MyPlugBuilder を作ることにする。

MyPlugBuilder は以下のように使う：

```ex:MyPlugBuilder-sample.ex
defmodule MyModule do
  use MyPlugBuilder
 
  plug :hello
  plug :world, good: :morning
end
 
IO.puts "plugs >>> #{inspect MyApp.plugs}"
# => plugs >>> [world: [good: :morning], hello: []]
```

`defmodule` 内で `use MyPlugBuilder` すると、 Plug.Builder に似た `plug` マクロを使えるようになる。同じ `defmodule` 内でなら何回でも `plug` を書ける。

定義したモジュールには `plugs` 関数が生える。この関数は、 `plug` に与えた atom とオプション引数のタプルをリストにして返す。

Plug.Builder はこのリストを元にモジュールを組み合わせていくのだが、今回はその処理までは再現しないことにする。

## MyPlugBuilder を実装する

実装の過程は省くが、 MyPlugBuilder の実装は以下のようになる。

```ex:MyPlugBuilder.ex
defmodule MyPlugBuilder do
 
  # `use MyPluginBuilder` すると呼ばれる
  defmacro __using__(_opts) do
    quote do
      import MyPlugBuilder, only: [plug: 1, plug: 2]
      Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
      @before_compile MyPlugBuilder
    end
  end
 
  # `plug` 本体
  defmacro plug(plug, opts \\ []) do
    quote do
      @plugs {unquote(plug), unquote(opts)}
    end
  end
 
  # `@before_compile MyPlugBuilder` 属性を含む翻訳単位がコンパイルされる直前に呼ばれる
  defmacro __before_compile__(env) do
    plugs = Module.get_attribute(env.module, :plugs)
    quote do
      def plugs, do: unquote(plugs)
    end
  end
end
```

この MyPlugBuilder を `use` して `plug` を使うとマクロがどのように展開されていくか、順を追って見ていくことにしよう。

## マクロの展開を追う

### 1. 初期状態

上で書いた MyPlugBuilder の使い方を再掲する。ここから1段階ずつマクロを展開していく。

```ex:1
defmodule MyModule do
  use MyPlugBuilder
 
  plug :hello
  plug :world, good: :morning
end
```

### 2. `use MyPlugBuilder` マクロを展開する

`use` は Elixir の組み込みマクロである。 `use MyPlugBuilder` は `require MyPlugBuilder` に展開される。また、指定したモジュールが `__using__/1` を定義しているなら、 `use` に与えたオプション引数リストを引数にしてそれを呼ぶ。

```ex:2
defmodule MyModule do
  # ----
  # use MyPlugBuilder
  # ---- ↓
  require MyPlugBuilder
  MyPlugBuilder.__using__([])
  # ----
 
  plug :hello
  plug :world, good: :morning
end
```
 
### 3. `MyPlugBuilder.__using__` マクロを展開する

`MyPlugBuilder.__using__` マクロは以下のように定義したのだった：

```ex:MyPlugBuilder.__using__
  defmacro __using__(_opts) do
    quote do
      import MyPlugBuilder, only: [plug: 1, plug: 2]
      Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
      @before_compile MyPlugBuilder
    end
  end
```

マクロは関数と似ているが、マクロの返り値は Elixir の式の内部表現でなければならない。式の内部表現は `quote do 式 end` で得ることができる（「式を quote する」という）。マクロが返した内部表現はマクロを呼び出した側に埋め込まれる。

`MyPlugBuilder.__using__` マクロは単に quote された式を返す。それを呼び出し側に埋め込むとこうなる：

```ex:3
defmodule MyModule do
  require MyPlugBuilder

  # ----
  # MyPlugBuilder.__using__([])
  # ---- ↓
  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  @before_compile MyPlugBuilder
  # ----
 
  plug :hello
  plug :world, good: :morning
end
```

`import` によって `defmodule MyModule` ブロック内で `plug` マクロを使えるようになる。

また `Module.register_attribute` によって、 `defmodule` ブロック内の `@plugs` 属性に指定された値を後で参照できるようになる。

`@before_compile` 属性のはたらきは後述する。
 
### 4. `plug` マクロを展開する

`plug` マクロの定義はこうだ：

```ex:plug
  defmacro plug(plug, opts \\ []) do
    quote do
      @plugs {unquote(plug), unquote(opts)}
    end
  end
```

全体を囲う `quote` の中で、一部を `unquote` している。 `unquote(plug)` はマクロの引数をこの場所にそのまま埋め込むという意味だ。 `plug` マクロを展開すると結局 `@plugs` 属性になる：

```ex:4
defmodule MyModule do
  require MyPlugBuilder

  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  @before_compile MyPlugBuilder
 
  # ----
  # plug :hello
  # plug :world, good: :morning
  # ---- ↓
  @plugs {:hello, []}
  @plugs {:world, [good: :morning]}
  # ----
end
```
 
### 5. `MyPlugBuilder.__before_compile__` マクロを注入する

`import` と `require` はスペシャルフォームマクロ（処理系に特別扱いされるマクロ）だ。普通のマクロのようには展開されないので、ここでは中身に深入りせず、展開が終わったということにしておく。

さて、すべてのマクロの展開が終わったので `MyModule` モジュールのコンパイルが始まる。だがその前にコンパイラは `@before_compile MyPlugBuilder` の処理を行う。

`@before_compile` 属性にモジュールを指定したとき、コンパイラはまずそのモジュールの `__before_comiple__/1` マクロを呼び出す。引数は `__ENV__` となる。そしてその返り値を `@before_compile` を含むモジュールの末尾に注入する。（参考： http://elixir-lang.org/docs/stable/elixir/Module.html ）

言い替えれば、モジュール内のどこかに `@before_compile MyPlugBuilder` を書けば、モジュール末尾に `MyPlugBuilder.__before_compile__(__ENV__)` を書いたのと同じということだ：

```ex:5
defmodule MyModule do
  require MyPlugBuilder

  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  # この属性によって……
  @before_compile MyPlugBuilder

  @plugs {:hello, []}
  @plugs {:world, [good: :morning]}
 
  # この位置に以下を書いたのと同じことになる
  MyPlugBuilder.__before_compile__(__ENV__)
end
```
 
### 6. `MyPlugBuilder.__before_compile__` マクロを展開する

`MyPlugBuilder.__before_compile__` の定義はこうだ：

```ex:MyPlugBuilder.__before_compile__
  defmacro __before_compile__(env) do
    plugs = Module.get_attribute(env.module, :plugs)
    quote do
      def plugs, do: unquote(plugs)
    end
  end
```

`Module.get_attribute` によって、 `env.module` の（つまり `MyModule` の） `@plugs` 属性に指定された値を得ておく。このマクロはその値を返す `plugs` 関数に展開される：

```ex:6 
defmodule MyModule do
  require MyPlugBuilder

  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  @before_compile MyPlugBuilder

  @plugs {:hello, []}
  @plugs {:world, [good: :morning]}

  # ----
  # MyPlugBuilder.__before_compile__(__ENV__)
  # ---- ↓
  def plugs, do: [world: [good: :morning], hello: []]
  # ----
end
```

### 7. コンパイル終了

モジュールをコンパイルすると属性は取り除かれる。最終的に `MyModule` は以下の形になる：

```ex:7
defmodule MyModule do
  require MyPlugBuilder

  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)

  def plugs, do: [world: [good: :morning], hello: []]
end
```

## おわりに

マクロを駆使することで以下のようにコードを変換できることが分かった：

```ex:Result
defmodule MyModule do
  use MyPlugBuilder
 
  plug :hello
  plug :world, good: :morning
end

# ↓↓↓

defmodule MyModule do
  require MyPlugBuilder

  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)

  def plugs, do: [world: [good: :morning], hello: []]
end
```

MyPlugBuilder は簡単のために `plug` マクロを `def plugs` に変換するだけとしたが、 Plug.Builder はモジュールを組み合わせるために追加の処理を行っている。とは言え、肝心の `plug` マクロの展開の流れは MyPlugBuilder で十分に再現できたと思う。

## 感想

マクロは面白いけど使いたくない。