Goal
Elixirのマクロで、関数を展開するサンプルを作成する。
Dev-Environment
OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.5
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
|> First step
|> Second step
|> Brief description
Plan
やりたいことを最初に記述します。
以下のような記述をすると、
alias_name:の値が関数名になり、マクロが展開されていくようにしたい。
alias_name:の値が関数名になり、マクロが展開されていくようにしたい。
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といったように・・・関数として展開していって欲しい。
hoge_def/1、huge_def/1といったように・・・関数として展開していって欲しい。
First step
第一段階のソースコードを作成します。
Example:
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:
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:
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:
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)ですが、最新だとソースコード自体が存在しませんでした。
参考にさせて頂いたソースコード(mapper.ex)ですが、最新だとソースコード自体が存在しませんでした。
同様の処理をどこかしらで行っているとは思いますが、
そこまで解読できていませんので・・・orz
そこまで解読できていませんので・・・orz
Bibliography
Phoenix-Framework v0.1.1 - mapper.ex