Goal
繰り返し処理を利用して、複数のdefmacroを展開させる。
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
Elixirの繰り返し処理を利用して、複数のマクロを一気に展開させます。
ただのTipsか一例です。
ただのTipsか一例です。
Phoenix-Frameworkのルーティングやmime-typeで使われています。
Index
Repeat defmacro
|> Example
|> Let’s Run
|> Extra
|> Example
|> Let’s Run
|> Extra
Example
実行するためのサンプルを作成します。
Example:
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記述を使って、繰り返し処理をさせることができる。
for記述を使って、繰り返し処理をさせることができる。
@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
Let’s Run
実際に実行して結果を見てみましょう。
Result:
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にします。
iex> quoted = quote do: IO.puts "hogehoge"
{{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}, [], ["hogehoge"]}
ASTを評価してみます。
iex> Code.eval_quoted(quoted)
hogehoge
{:ok, []}
ASTを分解します。
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があるので、さらに分解します。
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に分けます。
リストなので、headとtailに分けます。
iex> [head | tail] = context2
[{:__aliases__, [alias: false], [:IO]}, :puts]
headのASTを評価します。
iex> Code.eval_quoted(head)
{IO, []}
唐突に思いついてやってみました。
ここら辺を使って何かできないだろうか・・・う~ん、思いつかないな。
ここら辺を使って何かできないだろうか・・・う~ん、思いつかないな。
Speaking to oneself
同一処理の場合にのみ使えますね。
do~endを使って別の流れを作ることはできそうです。
ただ、複雑化しそうですが(笑)
ただ、複雑化しそうですが(笑)
そこまでしてやるなら別の方法があるような気もします。
Phoenix-Frameworkのルーティングは中々読み応えがあります。
まだ一機能でさえ全ては読み解けないです。
一部分でも読むのは中々大変なのに、全機能の把握とかどれだけの時間が掛かることでしょう(汗)
一部分でも読むのは中々大変なのに、全機能の把握とかどれだけの時間が掛かることでしょう(汗)
今回の手法は、Phoenixのソースコードに書いてありました。
比較的優しめの手法だったので記事にしています。
比較的優しめの手法だったので記事にしています。
役に立てば幸いです。