[英]Using guards in Elixir macros
I am working on macro which would take a function and add some additional functionality. 我正在研究宏,它将采用一个函数并添加一些额外的功能。 Eg.:
例如。:
This: 这个:
defstate this_works(a, b) do
a + b + 1
end
Should be converted to this: 应转换为:
def this_works(a, b) do
IO.puts("LOGGING whatever")
a + b + 1
end
This is what I have so far. 这就是我到目前为止所拥有的。 Try running this piece of code in iex:
尝试在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)
This works as expected. 这按预期工作。
Now, this module does not compile: 现在,这个模块没有编译:
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
The only change is that I added a guard and macro is unable to deal with that. 唯一的变化是我添加了一个后卫,宏无法处理。
How can I improve MyMacro.defstate
to make it work with a function with any number of guards? 如何改进
MyMacro.defstate
以使其适用于具有任意数量防护的功能?
If you inspect fn_atom
with the defstate this_fails(a, b) when 1 < 2
, you'll see that it's :when
instead of :this_fails
. 如果你
defstate this_fails(a, b) when 1 < 2
用defstate this_fails(a, b) when 1 < 2
检查fn_atom
,你会看到它是:when
而不是:this_fails
。 This is because of how when
expressions are represented in the Elixir AST: 这是因为如何
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]]}
You can fix this using some pattern matching: 您可以使用一些模式匹配来解决此问题:
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
Output: 输出:
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
(I also changed the unquote after def
to make sure the when
clause is preserved.) (我还在
def
之后更改了unquote,以确保保留when
子句。)
The call to defstate
is expanded at compile time to the things in the quote
block from your defmacro
. 对
defstate
的调用在编译时扩展到defstate
中的quote
块中的defmacro
。 As such, guard expressions will not be applied to the macro call directly, because at compile time, the function you're defining inside is not called. 因此,保护表达式不会直接应用于宏调用,因为在编译时,不会调用您在里面定义的函数。
So you have to grab the :when
tuple yourself and add the guards yourself: 所以你必须抓住
: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
Note how I match for a {:when, _, [ast, guards]}
tuple now. 注意我现在如何匹配
{:when, _, [ast, guards]}
元组。
When you call a macro with a guard, it will put the original ast inside the first item of the arguments list, and the guard expression inside the second item. 当你用一个守卫调用一个宏时,它会将原始的ast放在参数列表的第一项中,并将第二项中的守护表达式放在内。
Note that you'll still have to define a catch-all macro definition below this one in case you want to use your macro without guard clauses. 请注意,如果您想使用没有保护子句的宏,您仍然需要在此下面定义一个全能宏定义。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.