使用Macro.var创建的变量

时间:2018-10-12 19:48:06

标签: macros elixir

我正在尝试“模板化”一些冗余代码。我可以得到它来生成有关未使用变量的编译器警告,但是当我尝试使用那些相同的变量时,会出现“无效的带引号的表达式”。

defmodule Foo do
  defmacro __using__(variables) do
    quote do 
      def mapify(unquote(Enum.map(variables, &Macro.var(&1, nil))) do
         # will yield compiler warnings about unused variables
         %{}

         # invalid quoted expression
         unquote(Enum.map(variables, &{&1, Macro.var(&1, nil)}) |> Map.new)
      end
    end
  end
end

用法:

defmodule X do
  use Foo, [:alpha, :bravo, :charlie]
end

我怎么生成这个?

defmodule X do
  def mapify(alpha, bravo, charlie) do
    %{alpha: alpha, bravo: bravo, charlie: charlie}
  end
end

Elixir 1.7.3,Erlang 21.0

编辑:

这将生成功能正确的版本,但我担心每次调用都会运行Enum.zip ...

defmodule Foo do
  defmacro __using__(variables) do
    vars = Enum.map(variables, &Macro.var(&1, nil))
    result = quote do 
      def mapify(unquote(vars)) do
         Enum.zip(unquote(variables), unquote(vars)) |> Map.new
      end
    end

    IO.puts("Macro generated:\n#{result |> Macro.to_string}")
    result
  end
end

生成:

def(mapify([alpha, bravo, charlie])) do
  Enum.zip([:alpha, :bravo, :charlie], [alpha, bravo, charlie]) |> Map.new() 
end

编辑:

除非有人能弄清楚原始查询,否则请继续进行以下操作:

defmodule Foo do
  defmacro __using__(variables) do
    vars = Enum.map(variables, &Macro.var(&1, nil))
    pairs = Enum.map(variables, &{&1, Macro.var(&1, nil)}) 
    result = quote do 
      def mapify(unquote(vars)) do
         unquote(pairs) |> Map.new
      end
    end

    IO.puts("Macro generated:\n#{result |> Macro.to_string}")
    result
  end
end

生成的宏:

def(mapify([alpha, bravo, charlie])) do
  [alpha: alpha, bravo: bravo, charlie: charlie] |> Map.new()
end

1 个答案:

答案 0 :(得分:1)

正确答案provided by José

  

映射不是带引号的表达式,而是一个值。每次您拥有一个值时,都需要确保Macro.escape/1成为AST:

quote do
  def foo(unquote(args)) do
    [unquote(map_kw), unquote(Macro.escape(map))]
  end
end

也就是说,您只需要:

- unquote(pairs) |> Map.new
+ unquote(Macro.escape(pairs))

我的原始答案仍然有效,因为它可以手动构建地图的AST:

好吧,我无法找到更少的骇客方式,而且我不明白为什么关键字与地图不一样,但是您可以:

defmodule Foo do
  defmacro __using__(variables) do
    # allow sigil as input
    variables = Macro.expand(variables, __ENV__)

    vars = Enum.map(variables, &Macro.var(&1, nil))
    kw = for var <- variables, do: {var, Macro.var(var, nil)}
    # ⇓⇓⇓⇓ HERE ⇓⇓⇓⇓
    map = {:%{}, [], kw}

    result =
      quote do
        def mapify(unquote(vars)) do
          unquote(map)
        end
      end

    IO.puts("Macro generated:\n#{result |> Macro.to_string}")
    result
  end
end
defmodule Test, do: use Foo, ~w|alpha bravo charlie|a

请注意,我已更正此宏的另一个问题(在第一行中),以接受原子列表标记。

此外,您可能希望使用unquote中的Kernel.SpecialForms.unquote_splicing/1而不是def mapify(unquote_splicing(vars))来接受参数列表。


边注:我怀疑我是否理解此特定宏的用途。通用印章可能会更适合您的需求。或者,只要保持通用的Elixir语法就足够简洁了。

请阅读Andrea的文章,以了解正反两方面的内容(它包含指向回购的链接,其中包含~m个简短的地图标记历史记录。)