ElixirのDSLの基本構造。(Ecto.Schemaのネストしたマクロはどう作られている？)

Ectoでモデルを定義する際は、下記のようなDSLを使用します。

```ex
defmodule User do
  use Ecto.Schema

  schema "users" do
    field :name, :string
    field :age, :integer
  end
end
```

この類の、`schema`というマクロの下にさらに`field`というマクロが入れ子になっている構造は、いったいどのような仕組みで実現されているのか、下記いくつかのサンプルでご説明させて頂きたいと思います。

# サンプル1

```ex
defmodule A do
  defmacro __using__(_opts) do
    quote do
      import A
    end
  end
  defmacro macro1 do
    quote do
      def func1 do
        IO.inspect "func1 called"
      end
    end
  end
  defmacro macro2 do
    quote do
      def func2 do
        IO.inspect "func2 called"
      end
    end
  end
end

defmodule B do
  use A
  macro1
  macro2
end

B.func1
# => "func1 called"
B.func2
# => "func2 called"
```

ごくごくシンプルなマクロです。Aモジュールをuseして`macro1`を呼び出すと`func1`が定義され、`macro2`を呼び出すと`func2`が定義されます。

# サンプル2

次に、上記サンプル1のmacro1のquote式内で、macro2を呼び出してみましょう。

```ex
defmodule A do
  defmacro __using__(_opts) do
    quote do
      import A
    end
  end
  defmacro macro1 do
    quote do
      def func1 do
        IO.inspect "func1 called"
      end
      A.macro2 # macro2を呼び出す
    end
  end
  defmacro macro2 do
    quote do
      def func2 do
        IO.inspect "func2 called"
      end
    end
  end
end

defmodule B do
  use A
  macro1
end

B.func1
"func1 called"
B.func2
"func2 called"
```

問題なく呼び出すことが出来ます。`func1`も`func2`も正常に定義されています。マクロからマクロを呼び出すことは可能だということが分かりました。

# サンプル3

それでは本題です。マクロ内からマクロを呼び出せることが分かりましたので、次は`macro1`のdoブロック引数に`macro2`の呼び出しを埋め込んでみましょう。

```ex
defmodule A do
  defmacro __using__(_opts) do
    quote do
      import A
    end
  end
  defmacro macro1 [do: block]do
    quote do
      def func1 do
        IO.inspect "func1 called"
      end
      unquote(block) # 引数で渡されたdoブロックを展開する
    end
  end
  defmacro macro2 do
    quote do
      def func2 do
        IO.inspect "func2 called"
      end
    end
  end
end

defmodule B do
  use A
  macro1 do
    macro2 # doブロックにmacro2の呼び出しを含める
  end
end

B.func1
"func1 called"
B.func2
"func2 called"
```

無事に実行されました。

# サンプル4

最後のサンプルです。サンプル3までに得た情報を使って、Ecto.Schemaと似た感じのDSLを定義できるモジュールを作成してみましょう。

(下記で使用している`Module attributes`という機能に関しては、[こちら](http://elixir-lang.org/docs/stable/elixir/Module.html#register_attribute/3)の情報等をご参照ください)

```ex
defmodule Ecto.Schema do
  defmacro __using__(_opts) do
    quote do
      import Ecto.Schema
      Module.register_attribute __MODULE__, :fields, accumulate: true
      @before_compile unquote(__MODULE__)
    end
  end
  defmacro schema(_source, [do: block]) do
    quote do
      unquote(block)
    end
  end
  defmacro field(name) do
    quote do
      @fields unquote(name)
    end
  end
  defmacro __before_compile__(env) do
    fields = Module.get_attribute(env.module, :fields) |> Enum.reverse
    quote do
      def get_fields, do: unquote(Macro.escape(fields))
    end
  end
end

defmodule User do
  use Ecto.Schema
  schema "user" do
    field "col1"
    field "col2"
  end
end

User.get_fields
# => ["col1", "col2"]
```

`schema`マクロと`field`マクロを入れ子にして、`field`マクロに指定した列名のリストを、`get_fields`という関数を使用して取得することが出来ました。

# まとめ

ElixirのDSLにおけるマクロのネスト構造の基本は、上記のように**doブロックで別のマクロを渡してそれをquote内でunquoteする**という方式になるということがお分かり頂けたかと思います。

# 備考

上記の擬似的なEcto.Schemaモジュールに関しては、説明のためかなり簡略化してありますので、実際のEcto.Schemaモジュールがこの構造になっていることを保証するものではありません。

例えば実際のEcto.Schemaモジュールでは`__before_compile__`マクロは使用されていませんし、`unquote`以外にも`eval_quoted`が使われていたりします。ここら辺に関しては是非コードを実際に読んでみて頂ければと思います。