[Elixir]マクロで特定の値にマッチする関数を展開する (後、assertマクロの手法を少し)

#Goal
マクロで特定の値にマッチする関数を展開する。

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

#Wait a minute
マクロを使って特定の値にしかマッチしない関数を展開します。

覚えた技法の復習がてら作っているので、
一例として見て頂ければと思います。

#Index
Expand the matching function
|> Matching function
|> First step
|> Second step
|> Extra

##Matching function
関数の引数が特定の値にマッチしていないと、
関数にマッチしないようにしたい。

Example:

```elixir
defmodule SampleCode do
  def match_test(1, 2) do
    true
  end
end
```

Result:

```elixir
iex> SampleCode.match_test(1, 2)
true
```

Add Result:
実際に、マッチしない値を指定して関数を呼び出してみると...
関数にマッチしないとエラーを出してくれる。

```elixir
iex> SampleCode.match_test(2, 2)
** (FunctionClauseError) no function clause matching in SampleCode.match_test/2
    (sample_code) lib/sample_code.ex:2: SampleCode.match_test(2, 2)
iex> SampleCode.match_test(2, 1)
** (FunctionClauseError) no function clause matching in SampleCode.match_test/2
    (sample_code) lib/sample_code.ex:2: SampleCode.match_test(2, 1)
```

こんな感じで、引数の値にマッチしないと関数にマッチしないようにできる。
これをマクロで応用して、少し遊んでみましょう。

##First step
第一段階のソースコード。

マクロを呼び出すと、その引数値にしかマッチしない関数を展開したい。
そして、展開した関数を特定の値をキーにして実行するようにする。

Example:

```elixir
defmodule MatchMacroSample do
  defmacro set(key, do_clause) do
    run = Keyword.get(do_clause, :do, nil)

    quote do
      defp get(unquote(key)) do
        unquote(run)
      end
    end
  end
end

defmodule UseMatchMacroSample do
  import MatchMacroSample

  set "key1", do: "value1"
  set "key2", do: "value2"

  def get_key_match(key) do
    get(key)
  end
end
```

Result:

```elixir
iex> UseMatchMacroSample.get_key_match("key1")
"value1"
iex> UseMatchMacroSample.get_key_match("key2")
"value2"
iex> UseMatchMacroSample.get_key_match("key3")
** (FunctionClauseError) no function clause matching in UseMatchMacroSample.get/1
    (macro_sample) lib/macro_sample.ex:26: UseMatchMacroSample.get("key3")
```

##Second step
第二段階のソースコード。

動作は変わらないようにして、
呼び出す関数をuseで展開するようにする。

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

      def get_key_match(key) do
        get(key)
      end
    end
  end

  defmacro set(key, do_clause) do
    run = Keyword.get(do_clause, :do, nil)

    quote do
      defp get(unquote(key)) do
        unquote(run)
      end
    end
  end
end

defmodule UseMatchMacroSample do
  use MatchMacroSample

  set "key1", do: "value1"
  set "key2", do: "value2"
end
```

Result:

```elixir
iex> UseMatchMacroSample.get_key_match("key1")
"value1"
iex> UseMatchMacroSample.get_key_match("key2")
"value2"
iex> UseMatchMacroSample.get_key_match("key3")
** (FunctionClauseError) no function clause matching in UseMatchMacroSample.get/1
    (macro_sample) lib/macro_sample.ex:26: UseMatchMacroSample.get("key3")
```

はい完成~

##Extra
assertマクロを読んだメモ。

引用元: [elixir-lang/elixir - assertions.ex#L66](https://github.com/elixir-lang/elixir/blob/v1.0.5/lib/ex_unit/lib/ex_unit/assertions.ex#L66)

```elixir
defmacro assert({:=, _, [left, right]} = assertion) do
```

上記のような定義をしていた。
何これと思って、assertに渡す引数部分をquoteしてみた。

Try:

```elixir
iex> quote do: 1 == 2
{:==, [context: Elixir, import: Kernel], [1, 2]}
iex> quote do: 1 != 2
{:!=, [context: Elixir, import: Kernel], [1, 2]}
iex> quote do: 1 > 2
{:>, [context: Elixir, import: Kernel], [1, 2]}
iex> quote do: 1 < 2
{:<, [context: Elixir, import: Kernel], [1, 2]}
```

おｋ、分かった。
assertの引数部分をAST(タプル)にマッチさせているわけだ。

またassertions.exの中に気になる記述があったのでこれも試す。
こんな感じの記述。

Example:

```elixir
unquote(operator)(left, right)
```

Try:

```elixir
iex> quoted = quote do: unquote(:==)(1, 2)
{:==, [], [1, 2]}
iex> Macro.to_string(quoted)
"1 == 2"
```

Σ(ﾟДﾟ) (こんな心境だった。

それはともかく、マクロ内だとこんな書き方ができるわけですね・・・
ちなみに、普通に記述しても動かせなかった。

適当な例...

Try:

```elixir
iex> == 1, 2
** (SyntaxError) iex:10: syntax error before: '=='

iex> ==(1, 2)
** (SyntaxError) iex:10: syntax error before: '=='

iex> :== 1, 2
** (SyntaxError) iex:10: syntax error before: 1

iex> :==(1, 2)
** (SyntaxError) iex:10: syntax error before: '('

iex> (==)(1, 2)
** (SyntaxError) iex:10: syntax error before: '=='

iex> (==).(1, 2)
** (SyntaxError) iex:10: syntax error before: '=='

```

ASTにしているからこそできる方法ですね。
マクロをコンパイルする順番の話に発展するので、ここまで。

#Speaking to oneself
コード自体に意味はあまりありません。
最初に書いた通り、ただのサンプルですので。

何に使えるのかと言ったところですが...

Plugやルーティングなどで使ってる技法のようです。
どのソースコードかまでは知らない(笑)

そろそろ、次のレベルに行って打ちのめされてきても良さそうな気がしてきた。
少し難しめのマクロでも解読してみましょうかね...

assertマクロですが、驚きましたね。

引数にタプル(AST?)を使ってマッチングしていたり、マクロ特有(?)の書き方をしていたり、
あんな方法もあるんだなっと勉強になりました。

#Bibliography
[The Erlangelist - UNDERSTANDING ELIXIR MACROS, PART 2 - MICRO THEORY](http://www.theerlangelist.com/2014/06/understanding-elixir-macros-part-2.html)
[elixir-lang/elixir - assertions.ex#L66](https://github.com/elixir-lang/elixir/blob/v1.0.5/lib/ex_unit/lib/ex_unit/assertions.ex#L66)