![](/img/trans.png)
[英]Elixir build custom function with pattern matching and guards using macros?
[英]Using guards in Elixir macros
我正在研究宏,它將采用一個函數並添加一些額外的功能。 例如。:
這個:
defstate this_works(a, b) do
a + b + 1
end
應轉換為:
def this_works(a, b) do
IO.puts("LOGGING whatever")
a + b + 1
end
這就是我到目前為止所擁有的。 嘗試在iex中運行這段代碼:
defmodule MyMacro do
defmacro defstate(ast, do: block) do
{fn_atom, _} = Macro.decompose_call(ast)
quote do
def unquote(fn_atom)(var!(a), var!(b)) do
IO.puts("LOGGING")
unquote(block)
end
end
end
end
defmodule Test1 do
import MyMacro
defstate this_works(a, b) do
a + b + 1
end
end
Test.this_works(1, 2)
這按預期工作。
現在,這個模塊沒有編譯:
defmodule Test2 do
import MyMacro
defstate this_fails(a, b)
when 1 < 2
when 2 < 3
when 3 < 4 do
a + b + 1
end
end
唯一的變化是我添加了一個后衛,宏無法處理。
如何改進MyMacro.defstate
以使其適用於具有任意數量防護的功能?
如果你defstate this_fails(a, b) when 1 < 2
用defstate this_fails(a, b) when 1 < 2
檢查fn_atom
,你會看到它是:when
而不是:this_fails
。 這是因為如何when
表達的葯劑AST中表示:
iex(1)> quote do
...(1)> def foo, do: 1
...(1)> end
{:def, [context: Elixir, import: Kernel],
[{:foo, [context: Elixir], Elixir}, [do: 1]]}
iex(2)> quote do
...(2)> def foo when 1 < 2, do: 1
...(2)> end
{:def, [context: Elixir, import: Kernel],
[{:when, [context: Elixir],
[{:foo, [], Elixir}, {:<, [context: Elixir, import: Kernel], [1, 2]}]},
[do: 1]]}
您可以使用一些模式匹配來解決此問題:
defmodule MyMacro do
defmacro defstate(ast, do: block) do
f = case ast do
{:when, _, [{f, _, _} | _]} -> f
{f, _, _} -> f
end
quote do
def unquote(ast) do
IO.puts("LOGGING #{unquote(f)}")
unquote(block)
end
end
end
end
defmodule Test do
import MyMacro
defstate this_works(a, b) do
a + b + 1
end
defstate this_works_too(a, b) when a < 2 do
a + b + 1
end
end
defmodule A do
def main do
IO.inspect Test.this_works(1, 2)
IO.inspect Test.this_works_too(1, 2)
IO.inspect Test.this_works_too(3, 2)
end
end
A.main
輸出:
LOGGING this_works
4
LOGGING this_works_too
4
** (FunctionClauseError) no function clause matching in Test.this_works_too/2
The following arguments were given to Test.this_works_too/2:
# 1
3
# 2
2
a.exs:24: Test.this_works_too/2
a.exs:33: A.main/0
(elixir) lib/code.ex:376: Code.require_file/2
(我還在def
之后更改了unquote,以確保保留when
子句。)
對defstate
的調用在編譯時擴展到defstate
中的quote
塊中的defmacro
。 因此,保護表達式不會直接應用於宏調用,因為在編譯時,不會調用您在里面定義的函數。
所以你必須抓住:when
你自己元組並自己添加守衛時:
defmodule MyMacro do
defmacro defstate({:when, _, [ast, guards]}, do: block) do
{fn_atom, _} = Macro.decompose_call(ast)
quote do
def unquote(fn_atom)(var!(a), var!(b)) when unquote(guards) do
IO.puts("LOGGING")
unquote(block)
end
end
end
end
注意我現在如何匹配{:when, _, [ast, guards]}
元組。
當你用一個守衛調用一個宏時,它會將原始的ast放在參數列表的第一項中,並將第二項中的守護表達式放在內。
請注意,如果您想使用沒有保護子句的宏,您仍然需要在此下面定義一個全能宏定義。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.