[Elixir]マクロで関数を展開する一例

#Goal
Elixirのマクロで、関数を展開するサンプルを作成する。

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

#Wait a minute
このサンプルは、マクロで関数を生成し展開していくサンプルです。

このサンプル自体で何かができるものではありません。
予め、ご了承下さい。

#Index
Macro to expand the function
|> Plan
|> First step
|> Second step
|> Brief description

##Plan
やりたいことを最初に記述します。

以下のような記述をすると、
alias_name:の値が関数名になり、マクロが展開されていくようにしたい。

```elixir
defmodule UseSample do
  use Sample

  get "path/to/hoge", "ActionModule", [alias_name: "hoge"]
  get "path/to/huge", "ActionModule", [alias_name: "huge"]
end
```

上記のalias_name:だと、
hoge_def/1、huge_def/1といったように・・・関数として展開していって欲しい。

##First step
第一段階のソースコードを作成します。

Example:

```elixir
defmodule Sample do
  defmacro __using__(_options) do
    quote do
      import unquote(__MODULE__)
    end
  end

  defmacro match(_http_method, _path, _action, opts) do
    name = opts[:alias_name]
  
    quote do
      def unquote(String.to_atom "#{name}_def1")(arg) do
        IO.inspect "#{unquote(name)}_def1: #{arg}"
      end

      def unquote(String.to_atom "#{name}_def2")(arg) do
        IO.inspect "#{unquote(name)}_def2: #{arg}"
      end
    end
  end
end

defmodule UseSample do
  use Sample

  match :get, "path/to/hoge", "ActionModule", [alias_name: "hoge"]
end
```

Result:

```elixir
iex> import UseSample
nil
iex> hoge_def1(1)
"hoge_def1: 1"
"hoge_def1: 1"
iex> hoge_def2(1)
"hoge_def2: 1"
"hoge_def2: 1"
```

関数として展開されている。
出力結果が変わらないよう注意しながら、もう少し手を加える。

##Second step
第二段階のソースコードを記述します。

Example:

```elixir
defmodule Sample do
  defmacro __using__(_options) do
    quote do
      Module.register_attribute __MODULE__, :routes, accumulate: true,
                                                     persist: false
      import unquote(__MODULE__)
      @before_compile unquote(__MODULE__)
    end
  end

  defmacro __before_compile__(env) do
    routes = Module.get_attribute(env.module, :routes)
  
    for route <- routes do
      {http_method, path, action, opts} = route
      quote do
        match(unquote(http_method), unquote(path), unquote(action), unquote(opts))
      end
    end
  end

  defmacro match(_http_method, _path, _action, opts) do
    name = opts[:alias_name]
    if name do
      quote do
        def unquote(String.to_atom "#{name}_def1")(arg) do
          IO.inspect "#{unquote(name)}_def1: #{arg}"
        end

        def unquote(String.to_atom "#{name}_def2")(arg) do
          IO.inspect "#{unquote(name)}_def2: #{arg}"
        end
      end
    end
  end

  defmacro get(path, action, opts) do
    add_route(:get, path, action, opts)
  end

  defp add_route(http_method, path, action, opts) do
    quote do
      @routes {unquote(http_method), unquote(path), unquote(action), unquote(opts)}
    end
  end
end

defmodule UseSample do
  use Sample

  get "path/to/hoge", "ActionModule", [alias_name: "hoge"]
  get "path/to/huge", "ActionModule", [alias_name: "huge"]
end
```

Result:

```elixir
iex> import UseSample
nil
iex> hoge_def1(1)
"hoge_def1: 1"
"hoge_def1: 1"
iex> hoge_def2(2)
"hoge_def2: 2"
"hoge_def2: 2"
iex> huge_def1(1)
"huge_def1: 1"
"huge_def1: 1"
iex> huge_def2(2)
"huge_def2: 2"
"huge_def2: 2"
```

これで完成(?)です。

動作の流れについては次の項目。
(説明を全くしてなかったですね)

##Brief description
簡単な動作の流れを記述します。

1. `UseSample`モジュールで、`Sample`モジュールを`use`している (`Sample.using/1`が展開される)
2. `Sample.using/1`では、アトリビュートの登録、インポート、`Sample.before_compile/1`の設定をしている
3. `UseSample`モジュールで、`Sample.get/3`マクロを呼び出している
4. `Sample.add_route/4`では、アトリビュートの設定を行っている
5. `Sample.before_compie/1`では、アトリビュートの値を取得し、`Sample.match/4`を呼び出し引数として渡している
6. `Sample.match/4`は、`alias_name:`の値に応じた関数を展開している

簡単に流れを説明すると・・・こんな感じですね。
(説明足りなかったらコメント下さい)

#Speaking to oneself
Phoenix-Framework v0.1.1のソースコードを参考にさせて頂きました。
参考にさせて頂いたソースコード(mapper.ex)ですが、最新だとソースコード自体が存在しませんでした。

同様の処理をどこかしらで行っているとは思いますが、
そこまで解読できていません・・・orz

#Bibliography
Phoenix-Framework v0.1.1 - mapper.ex