【发布时间】:2018-12-08 11:57:37
【问题描述】:
我有一张地图,我想将单一事实来源用于几个功能。假设它是:
source_of_truth = %{a: 10, b: 20}
我希望该映射的键是 EctoEnum 的值。 EctoEnum 提供了一个宏 defenum,我应该像这样使用它:
defenum(
EnumModule,
:enum_name,
[:a, :b]
)
我不想重复[:a, :b] 部分。我想像这样使用地图中的键:
defenum(
EnumModule,
:enum_name,
Map.keys(source_of_truth)
)
它不起作用,因为 defenum 宏需要一个普通列表。
我想我可以通过这样定义自己的宏来做到这一点:
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_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
不知道我可以把? 放在哪里。我不能只放values,因为它是一个变量而不是一个列表。我也不能输入unquote(values)。
一种可行的解决方案是这样的:
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 是一种不好的做法。
[编辑]
Macro.expand 的解决方案也不起作用,因为它实际上并不评估任何东西。扩展停止于:
Expanded: {{:., [],
[
{:__aliases__, [alias: false, counter: -576460752303357631], [:Module]},
:get_attribute
]}, [],
[
{:__MODULE__, [counter: -576460752303357631], Kernel},
:keys,
[
{:{}, [],
[
TestModule,
:__MODULE__,
0,
[
file: '...',
line: 16
]
]}
]
]}
所以它没有像我们预期的那样扩展到列表。
[\编辑]
什么是解决这个问题的好方法?
【问题讨论】: