【发布时间】:2016-04-25 20:13:24
【问题描述】:
继续 Elixir 文档中的 Binding and unquote fragments 示例...
我们有一个基于关键字列表定义函数的宏。
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
现在让我们将宏的主体移动到辅助函数中。
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
很好,一切仍然有效。但是现在如果我们想在将kv 传递给私有辅助函数之前创建另一个修改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
这行不通。 Elixir 说 _devkv 没有定义。我们可以通过使用完全限定的函数名来修复:
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
但后来 Elixir 抱怨 MacroFun._defkv 是私有的。所以我们把它改成 public,但是它仍然不起作用,因为辅助方法 _devkv 将引用的代码返回给我们的宏 def_modified_kv,它本身被引用了!
所以我们可以通过评估辅助函数返回的代码(最终代码)来解决这个问题:
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之外还有更好的方法吗?
我觉得我做错了什么。
感谢您的帮助。
【问题讨论】:
标签: macros metaprogramming elixir