11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-09-04

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
  ...
  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

Github - phoenixframework/phoenix - phoenix/lib/phoenix/router.ex

11
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?