[英]Using helper functions in Elixir macros
Continuing the Binding and unquote fragments example from the Elixir documentation... 继续Elixir文档中的Binding和unquote片段示例...
We have a macro that defines functions based on a Keyword list. 我们有一个宏,用于根据关键字列表定义功能。
defmodule MacroFun do
defmacro defkv(kv) do
quote bind_quoted: [kv: kv] do
Enum.each kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end
end
end
defmodule Runner do
require MacroFun
kv = [foo: 1, bar: 2]
MacroFun.defkv(kv)
end
Runner.foo
Now let's move the body of the macro to a helper function. 现在,让我们将宏的主体移至辅助函数。
defmacro defkv(kv) do
_defkv(kv)
end
defp _defkv(kv) do
quote bind_quoted: [kv: kv] do
Enum.each kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end
end
Great, everything still works. 太好了,一切仍然有效。 But now what if we want to make another macro that modifies
kv
before passing it to the private helper function: 但是现在,如果我们要制作另一个修改
kv
宏,然后再将其传递给私有帮助器函数,该怎么办:
defmacro def_modified_kv(kv) do
quote bind_quoted: [kv: kv] do
modified_kv = Enum.map kv, fn {k, v} -> {k, v + 1} end
_defkv(modified_kv)
end
end
That doesn't work. 那不行 Elixir says
_devkv
is not defined. Elixir说
_devkv
没有定义。 We can fix by using a fully qualified function name: 我们可以使用完全限定的函数名称来修复:
defmacro def_modified_kv(kv) do
quote bind_quoted: [kv: kv] do
modified_kv = Enum.map kv, fn {k, v} -> {k, v + 1} end
MacroFun._defkv(modified_kv)
end
end
But then Elixir complains that MacroFun._defkv
is private. 但是随后Elixir抱怨
MacroFun._defkv
是私有的。 So we change it to public, but it still doesn't work because the helper method _devkv
returns quoted code to our macro def_modified_kv
which itself is quoted! 因此,我们将其更改为public,但是它仍然无法正常工作,因为辅助方法
_devkv
将引用的代码返回到宏def_modified_kv
,该宏本身被引用了!
So we can fix that by eval'ing the code returned by the helper function (final code): 因此,我们可以通过评估辅助函数返回的代码(最终代码)来解决此问题:
defmodule MacroFun do
defmacro defkv(kv) do
_defkv(kv)
end
defmacro def_modified_kv(kv) do
quote bind_quoted: [kv: kv] do
modified_kv = Enum.map kv, fn {k, v} -> {k, v + 1} end
MacroFun._defkv(modified_kv) |> Code.eval_quoted([], __ENV__) |> elem(0)
end
end
def _defkv(kv) do
quote bind_quoted: [kv: kv] do
Enum.each kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end
end
end
Code.eval_quoted
? Code.eval_quoted
之外,还有更好的方法吗? I feel like I'm doing something wrong. 我觉得我做错了什么。
Thanks for the help. 谢谢您的帮助。
When you import a module, you can only import public methods. 导入模块时,只能导入公共方法。
Macros are a compile time feature, they write actually write code (or AST) in the module. 宏是编译时的功能,它们在模块中实际写入代码(或AST)。 So, the context of the macro when it's expanded, is where you're calling/using it.
因此,宏展开时的上下文就是您调用/使用它的地方。 Thus, if you use a macro (not defined in)
SomeOtherModule
that's the context of macro expansion. 因此,如果使用宏(未定义)
SomeOtherModule
,则它是宏扩展的上下文。 In SomeOtherModule
you would need to import MacroFun
to call its functions locally. 在
SomeOtherModule
您需要import MacroFun
才能在本地调用其功能。
The code below works in Elixir >= 1.2.0 下面的代码在Elixir> = 1.2.0中有效
Eg 例如
defmodule MacroFun do
defmacro defkv(kv) do
_defkv(kv)
end
defp _defkv(kv) do
quote bind_quoted: [kv: kv] do
Enum.each kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end
end
defmacro def_modified_kv(kv) do
quote bind_quoted: [kv: kv] do
modified_kv = Enum.map kv, fn {k, v} -> {k, v + 1} end
defkv(modified_kv)
end
end
end
defmodule Runner do
import MacroFun
kv = [foo: 1, bar: 2]
def_modified_kv(kv)
end
Hope this answers your questions! 希望这能回答您的问题! Good luck.
祝好运。
Updated a couple times because macros are a little confusing! 更新了两次,因为宏有些混乱!
1) By using require
, you've told the compiler to compile and make available MacroFun
s public macros and functions. 1)通过使用
require
,您已经告诉编译器编译并提供MacroFun
的公共宏和函数。 Note that the functions would be available anyway though: 请注意,这些功能仍然可以使用:
Notice that usually modules should not be required before usage, the only exception is if you want to use the macros from a module.
请注意,通常在使用前不需要模块,唯一的例外是如果要使用模块中的宏。
2) They are available through their FQN 2)可通过FQN获得
3) Here's my take on it: 3)这是我的看法:
defmodule MicroFun do
defmacro defkv(kv) do
_defkv(kv)
end
defp _defkv(kv) do
quote bind_quoted: [kv: kv] do
Enum.each kv, fn {k,v} ->
def unquote(k)(), do: unquote(v)
end
end
end
defmacro modifier(kv) do
quote bind_quoted: [kv: kv] do
Enum.map kv, fn {k,v} -> {k, v+1} end
end
end
end
defmodule Runner do
require MicroFun
[foo: 1, bar: 2]
|> MicroFun.modifier
|> MicroFun.defkv
end
The idea is that you don't need to nest macros/functions when you can just pipe them. 这个想法是,当您可以通过管道传递宏/函数时,就不需要嵌套它们。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.