Macros 将计算出的列表传递给Elixir宏
我有一个地图,我想用一个单一的真理来源来实现几个函数。比如说:Macros 将计算出的列表传递给Elixir宏,macros,elixir,Macros,Elixir,我有一个地图,我想用一个单一的真理来源来实现几个函数。比如说: source_of_truth = %{a: 10, b: 20} 我希望地图的键是的值。EctoEnum提供了一个宏defenum,我应该这样使用它: defenum( EnumModule, :enum_name, [:a, :b] ) defenum( EnumModule, :enum_name, Map.keys(source_of_truth) )
source_of_truth = %{a: 10, b: 20}
我希望地图的键是的值。EctoEnum提供了一个宏defenum
,我应该这样使用它:
defenum(
EnumModule,
:enum_name,
[:a, :b]
)
defenum(
EnumModule,
:enum_name,
Map.keys(source_of_truth)
)
defmacro dynamic_enum(enum_module, enum_name, enum_values) do
quote do
defenum(
unquote(enum_module),
unquote(enum_name),
unquote(enum_values)
)
end
end
我不想重复第二部分。我想使用地图上的钥匙,而不是像这样:
defenum(
EnumModule,
:enum_name,
[:a, :b]
)
defenum(
EnumModule,
:enum_name,
Map.keys(source_of_truth)
)
defmacro dynamic_enum(enum_module, enum_name, enum_values) do
quote do
defenum(
unquote(enum_module),
unquote(enum_name),
unquote(enum_values)
)
end
end
它不起作用,因为defenum
宏需要一个简单的列表
我想我可以这样定义自己的宏:
defenum(
EnumModule,
:enum_name,
[:a, :b]
)
defenum(
EnumModule,
:enum_name,
Map.keys(source_of_truth)
)
defmacro dynamic_enum(enum_module, enum_name, enum_values) do
quote do
defenum(
unquote(enum_module),
unquote(enum_name),
unquote(enum_values)
)
end
end
然后打电话:
dynamic_enum(EnumModule, :enum_name, Map.keys(source_of_truth))
但是,它做了同样的事情:enum\u values
不是一个预计算的列表,而是Map.get
的AST。我的下一个方法是:
defmacro dynamic_enum(enum_module, enum_name, enum_values) do
quote do
values = unquote(enum_values)
defenum(
unquote(enum_module),
unquote(enum_name),
?
)
end
end
不确定我能把什么放在的位置?
上。我不能只把值放进去,因为它是一个变量,而不是一个列表。我也不能把unquote(值)
放进去
一种可行的解决方案是:
defmacro dynamic_enum(enum_module, enum_name, enum_values) do
{values, _} = Code.eval_quoted(enum_values)
quote do
defenum(
unquote(enum_module),
unquote(enum_name),
unquote(values)
)
end
end
然而,文件说在宏中使用eval_quoted
是一种不好的做法
[编辑]
带有宏.expand
的解决方案也不起作用,因为它实际上不计算任何值。扩展将在以下位置停止:
Expanded: {{:., [],
[
{:__aliases__, [alias: false, counter: -576460752303357631], [:Module]},
:get_attribute
]}, [],
[
{:__MODULE__, [counter: -576460752303357631], Kernel},
:keys,
[
{:{}, [],
[
TestModule,
:__MODULE__,
0,
[
file: '...',
line: 16
]
]}
]
]}
因此,它并没有像我们预期的那样扩展到列表中
[\EDIT]
解决这个问题的好办法是什么?我不久前也遇到过同样的问题。基本上,您可以在quote
中构建语法树,使用unquote
注入动态值,然后使用code.eval_quote
评估宏:
options = Map.keys(source_of_truth)
Code.eval_quoted(
quote do
EctoEnum.defenum(MyEnum, :type_name, unquote(options))
end,
[],
__ENV__
)
如以下文件所述:
扩展了以下内容:
- 宏(本地或远程)
- 扩展别名(如果可能)并返回原子
- 编译环境宏(
\uuuuuu调用程序\uuuuuuuuu/0
,\uuuuuuu目录\uuuuuuuuuu/0
,\uuuuuuuuu环境/0
和\uuuuuuuuuuuu模块/0
)
- 模块属性读取器(
@foo
)
重点是我的。因此,可以将模块属性与Macro.expand/2
一起使用
defmacro dynamic_enum(enum_module, enum_name, enum_values) do
IO.inspect(enum_values, label: "Passed")
expanded = Macro.expand(enum_values, __CALLER__)
IO.inspect(expanded, label: "Expanded")
quote do
defenum(
unquote(enum_module),
unquote(enum_name),
unquote(expanded)
)
end
end
把它叫做:
@source_of_truth %{a: 10, b: 20}
@keys Map.keys(@source_of_truth)
def test_attr do
dynamic_enum(EnumModuleA, :enum_name_a, @keys)
end
FWIW,完整代码:
$\cat lib/eenum.ex
defmodule Eenum do
import EctoEnum
defmacro dynamic_enum(enum_module, enum_name, enum_values) do
IO.inspect(enum_values, label: "Passed")
expanded = Macro.expand(enum_values, __CALLER__)
IO.inspect(expanded, label: "Expanded")
quote do
defenum(
unquote(enum_module),
unquote(enum_name),
unquote(expanded)
)
end
end
end
defmodule Tester do
import Eenum
@source_of_truth %{a: 10, b: 20}
@keys Map.keys(@source_of_truth)
def test_attr do
dynamic_enum(EnumModuleA, :enum_name_a, @keys)
end
end
$\cat lib/tester.ex
defmodule Eenum do
import EctoEnum
defmacro dynamic_enum(enum_module, enum_name, enum_values) do
IO.inspect(enum_values, label: "Passed")
expanded = Macro.expand(enum_values, __CALLER__)
IO.inspect(expanded, label: "Expanded")
quote do
defenum(
unquote(enum_module),
unquote(enum_name),
unquote(expanded)
)
end
end
end
defmodule Tester do
import Eenum
@source_of_truth %{a: 10, b: 20}
@keys Map.keys(@source_of_truth)
def test_attr do
dynamic_enum(EnumModuleA, :enum_name_a, @keys)
end
end
FWIW 2。要能够从模块作用域调用如上所示的dynamic_enum
,您只需(出乎意料地:)另一个模块作用域,该作用域在宏调用时已编译:
defmodule Defs do
@source_of_truth %{a: 10, b: 20}
@keys Map.keys(@source_of_truth)
defmacro keys, do: Macro.expand(@keys, __CALLER__)
end
defmodule Tester do
import Defs
import Eenum
dynamic_enum(EnumModuleA, :enum_name_a, keys())
end
FWIW 3。后者(带有定义的显式模块)即使不需要模块属性也可以工作:
defmodule Defs do
defmacro keys, do: Macro.expand(Map.keys(%{a: 10, b: 20}), __CALLER__)
end
defmodule Tester do
import Defs
import Eenum
dynamic_enum(EnumModuleA, :enum_name_a, keys())
end
经验法则是当您发现自己需要调用code.eval\u quoted/3
时,将此代码放入独立模块,并让编译器为您调用此代码编译。对于在模块级工作的函数,对于模块级,它应该放在另一个模块中,以使模块上下文(aka\uuuu调用者uuuu
和\uuu环境uu
)可用。所以您建议不要在模块体中使用defmacro
,而只使用eval\u quoted
?我将有几个其他的例子非常相似,所以我想在宏中准备好代码。是的,我不认为它那么冗长。请注意,这可以转换为一行,我只是在这里把它放在这里让它更清楚。但我承认,我没有找到一种方法将其整齐地包装到宏中。经验法则是,当您发现自己需要调用code.eval_quoted/3
时,将此代码放入独立模块中,让编译器为您调用此代码编译。对于在模块级工作的函数,对于模块级,应将其放入另一个模块中,以使模块上下文(aka\uuuuuu调用者\uuuuuuuuu
和\uuuuuuuu环境
)可用。这不起作用。它将延长AST,直到不再可能进行扩展,但它不会计算到列表中。我编辑了这个问题以显示它会导致什么错误;我已经测试过了。你确定你通过了@keys
吗?这是\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu>上下文中定义的列表?FWIW,我已经发布了我用于测试的完整代码。欢迎使用。接下来:调用方上下文是一个实际存储的上下文/环境,需要实际的模块来存储它,因为不能将任何任意数据附加到函数。这就是为什么模块属性起作用而函数调用不起作用的原因。这甚至更好!我的下一个问题是如何将定义移动到另一个模块,因为真相的来源可能很大!太神了