Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/elixir/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Macros 将计算出的列表传递给Elixir宏_Macros_Elixir - Fatal编程技术网

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,我已经发布了我用于测试的完整代码。欢迎使用。接下来:调用方上下文是一个实际存储的上下文/环境,需要实际的模块来存储它,因为不能将任何任意数据附加到函数。这就是为什么模块属性起作用而函数调用不起作用的原因。这甚至更好!我的下一个问题是如何将定义移动到另一个模块,因为真相的来源可能很大!太神了