繁体   English   中英

将计算列表传递给Elixir宏

[英]Passing computed list to an Elixir macro

我有一张地图,我想用几个功能的单一事实来源。 让我们说它是:

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
        ]
      ]}
   ]
 ]}

所以它没有像我们预期的那样扩展到列表。

[\\编辑]

这个问题有什么好的解决方案?

Macro.expand/2的文档中所述

扩展了以下内容:

  • 宏(本地或远程)
  • 别名扩展(如果可能)并返回原子
  • 编译环境宏( __CALLER__/0__DIR__/0 __CALLER__/0__DIR__/0 __ENV__/0__MODULE__/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

$ \\cat lib/tester.ex

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_quoted/3 ,将此代码放入独立模块并让编译器为您调用此代码编译。 对于函数是在模块级别工作,对于模块级别,它应该放入另一个模块以使模块上下文(aka __CALLER____ENV__ )可用。

我曾经和同样的问题争吵了一会儿。 基本上,您可以在quote构建语法树,使用unquote注入动态值,然后使用Code.eval_quoted来评估宏:

options = Map.keys(source_of_truth)

Code.eval_quoted(
  quote do
    EctoEnum.defenum(MyEnum, :type_name, unquote(options))
  end,
  [],
  __ENV__
)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM