[Elixir]繰り返し処理を利用してdefmacroを展開する

#Goal
繰り返し処理を利用して、複数のdefmacroを展開させる。

#Dev-Environment
OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.5

#Wait a minute
Elixirの繰り返し処理を利用して、複数のマクロを一気に展開させます。
ただのTipsか一例です。

Phoenix-Frameworkのルーティングやmime-typeで使われています。

#Index
Repeat defmacro
|> Example
|> Let's Run
|> Extra

##Example
実行するためのサンプルを作成します。

Example:

```elixir
defmodule ForExpandMacroSample do
  defmacro __using__(_) do
    quote do
      import unquote(__MODULE__)
    end
  end

  @function_names [:hoge, :huge, :foo, :bar]

  for function_name <- @function_names do
    @doc """
    Write doc #{function_name}.
    """
    defmacro unquote(function_name)(arg1, arg2, options \\ []) do
      do_function(:func, unquote(function_name), arg1, arg2, options)
    end
  end

  defp do_function(func, function_name, arg1, arg2, options) do
    quote do
      %{func: unquote(func),
        function_name: unquote(function_name),
        arg1: unquote(arg1),
        arg2: unquote(arg2),
        options: unquote(options)}
    end
  end
end

defmodule UseForExpandMacroSample do
  use ForExpandMacroSample

  def test() do
    IO.inspect hoge("arg1", "arg2")
    IO.inspect huge("arg1", "arg2", huge_option: "hugehuge")
    IO.inspect foo("arg1", "arg2", foo_option: "foo")
    IO.inspect bar("arg1", "arg2")
  end
end
```

Description:
肝の部分は、この部分。
for記述を使って、繰り返し処理をさせることができる。

```elixir
@function_names [:hoge, :huge, :foo, :bar]

for function_name <- @function_names do
  ...
  defmacro unquote(function_name)(arg1, arg2, options \\ []) do
    do_function(:func, unquote(function_name), arg1, arg2, options)
  end
end
```

##Let's Run
実際に実行して結果を見てみましょう。

Result:

```elixir
iex> UseForExpandMacroSample.test
%{arg1: "arg1", arg2: "arg2", func: :func, function_name: :hoge, options: []}
%{arg1: "arg1", arg2: "arg2", func: :func, function_name: :huge,
  options: [huge_option: "hugehuge"]}
%{arg1: "arg1", arg2: "arg2", func: :func, function_name: :foo,
  options: [foo_option: "foo"]}
%{arg1: "arg1", arg2: "arg2", func: :func, function_name: :bar, options: []}
%{arg1: "arg1", arg2: "arg2", func: :func, function_name: :bar, options: []}
```

ちゃんとマップが返ってきてますね。

##Extra
記事に関連性のないおまけ。
関数をquoteして、ASTを分解して遊びます。

Try:

IO.putsをquoteしてASTにします。

```elixir
iex> quoted = quote do: IO.puts "hogehoge"
{{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}, [], ["hogehoge"]}
```

ASTを評価してみます。

```elixir
iex> Code.eval_quoted(quoted)
hogehoge
{:ok, []}
```

ASTを分解します。

```elixir
iex> {name, meta, context} = quoted
{{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}, [], ["hogehoge"]}
iex> name
{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}
iex> meta
[]
iex> context
["hogehoge"]
```

nameの中にASTがあるので、さらに分解します。

```elixir
iex> {name2, meta2, context2} = name
{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}
iex> name2
:.
iex> meta2
[]
iex> context2
[{:__aliases__, [alias: false], [:IO]}, :puts]
```

context2の中にASTがあります。
リストなので、headとtailに分けます。

```elixir
iex> [head | tail] = context2
[{:__aliases__, [alias: false], [:IO]}, :puts]
```

headのASTを評価します。

```elixir
iex> Code.eval_quoted(head)
{IO, []}
```

唐突に思いついてやってみました。
ここら辺を使って何かできないだろうか・・・う~ん、思いつかないな。

#Speaking to oneself
引数値が異なる、同一処理の場合に使えそうですね。

do~endを使って別の流れを作ることはできそうです。
ただ、複雑化しそうですが(笑)

そこまでしてやるなら別の方法があるような気もします。

Phoenix-Frameworkのルーティングは中々読み応えがあります。

まだ一機能でさえ全ては読み解けないです。
一部分でも読むのは中々大変なのに、全機能の把握とかどれだけの時間が掛かることでしょう(汗)

今回の手法は、Phoenixのソースコードに書いてありました。
比較的優しめの手法だったので記事にしています。

役に立てば幸いです。

#Bibliography
[Github - phoenixframework/phoenix - phoenix/lib/phoenix/router.ex](https://github.com/phoenixframework/phoenix/blob/master/lib/phoenix/router.ex)