Goal
マクロで特定の値にマッチする関数を展開する。
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
Expand the matching function
|> Matching function
|> First step
|> Second step
|> Extra
|> Matching function
|> First step
|> Second step
|> Extra
Matching function
関数の引数が特定の値にマッチしていないと、
関数にマッチしないようにしたい。
関数にマッチしないようにしたい。
Example:
defmodule SampleCode do
def match_test(1, 2) do
true
end
end
Result:
iex> SampleCode.match_test(1, 2)
true
Add Result:
実際に、マッチしない値を指定して関数を呼び出してみると…
関数にマッチしないとエラーを出してくれる。
関数にマッチしないとエラーを出してくれる。
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:
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:
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で展開するようにする。
呼び出す関数をuseで展開するようにする。
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:
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
defmacro assert({:=, _, [left, right]} = assertion) do
上記のような定義をしていた。
何これと思って、assertに渡す引数部分をquoteしてみた。
何これと思って、assertに渡す引数部分をquoteしてみた。
Try:
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]}
おk、分かった。
assertの引数部分をAST(タプル)にマッチさせているわけだ。
assertの引数部分をAST(タプル)にマッチさせているわけだ。
またassertions.exの中に気になる記述があったのでこれも試す。
こんな感じの記述。
こんな感じの記述。
Example:
unquote(operator)(left, right)
Try:
iex> quote do: unquote(:==)(1, 2)
{:==, [], [1, 2]}
iex> quoted = quote do: unquote(:==)(1, 2)
{:==, [], [1, 2]}
iex> Macro.to_string(quoted)
"1 == 2"
Σ(゚Д゚) (こんな心境だった。
それはともかく、マクロ内だとこんな書き方ができるわけですね・・・
ちなみに、普通に記述しても動かせなかった。
ちなみに、普通に記述しても動かせなかった。
適当な例…
Try:
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?)を使ってマッチングしていたり、マクロ特有(?)の書き方をしていたり、
あんな方法もあるんだなっと勉強になりました。
あんな方法もあるんだなっと勉強になりました。