スポンサーリンク

2015年9月4日

[Elixir]Repeat defmacro

とある錬金術師の万能薬(Elixir)

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:

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記述を使って、繰り返し処理をさせることができる。
@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に分けます。
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のソースコードに書いてありました。
比較的優しめの手法だったので記事にしています。
役に立てば幸いです。

Bibliography

人気の投稿