我正在研究宏,它将采用一个函数并添加一些额外的功能。例如:
此:
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
以使其适用于具有任意数量警卫的功能?
答案 0 :(得分:3)
如果您使用fn_atom
检查defstate this_fails(a, b) when 1 < 2
,则会看到它是:when
而不是:this_fails
。这是因为Elixir AST中when
表达式的表示方式:
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
之后更改了取消引用,以确保保留when
子句。)
答案 1 :(得分:0)
对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放在参数列表的第一个项目中,并将守护表达式放在第二个项目中。
请注意,如果你想使用没有保护条款的宏,你仍然需要在这个下面定义一个全能宏定义。