【问题标题】:Using unquote_splicing in macros with modified lists在带有修改列表的宏中使用 unquote_splicing
【发布时间】:2020-03-21 08:20:27
【问题描述】:

Elixir 的 unquote_splicing 在直接取消引用传递的列表时可以正常工作。例如,调用Test1.wrap([1,2,3]) 下面的宏将正确返回[0,0,0,1,2,3,0,0,0]

defmodule Test1 do
  defmacro wrap(nums) do
    quote do
      [0,0,0, unquote_splicing(nums), 0,0,0]
    end
  end
end

但是如果我对列表进行任何更改然后尝试调用unquote_splicing,Elixir 甚至都不会让我定义宏:

defmodule Test2 do
  defmacro double_wrap(nums) do
    quote do
      doubles = Enum.map(unquote(nums), & &1*2)

      [0,0,0, unquote_splicing(doubles), 0,0,0]
    end
  end
end

这会直接引发编译错误:

warning: variable "doubles" does not exist and is being expanded to "doubles()", please use parentheses to remove the ambiguity or change the variable name
  iex:37: Test.double_wrap/1

** (CompileError) iex:37: undefined function doubles/0
    (elixir) src/elixir_locals.erl:108: :elixir_locals."-ensure_no_undefined_local/3-lc$^0/1-0-"/2
    (elixir) src/elixir_locals.erl:108: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3

到目前为止,我已经尝试了很多东西,例如:

  • 使用嵌套引号
  • 使用bind_quoted
  • 浏览 MacroCode 文档

但没有任何效果,我无法弄清楚我做错了什么。

【问题讨论】:

    标签: macros elixir


    【解决方案1】:

    宏返回的内容直接注入到调用代码的位置。 Kernel.SpecialForms.unquote/1(以及unquote_splicing/1)用于访问调用者上下文。这就是您的代码引发的原因:调用者上下文中没有定义局部变量 doubles

    您可以做的是在quote 块之外声明doubles

    defmodule D do
      defmacro double_wrap(nums) do
        doubles = Enum.map(nums, & &1*2)
        quote do
          [0,0,0, unquote_splicing(doubles), 0,0,0]
        end
      end
    end
    
    require D
    D.double_wrap [1,2,3]
    #⇒ [0, 0, 0, 2, 4, 6, 0, 0, 0]
    

    也就是说,这很高兴解决了:

    doubles = [1,2,3]
    quote do: [0,0,0, unquote_splicing(doubles), 0,0,0]
    #⇒ [0, 0, 0, 1, 2, 3, 0, 0, 0]
    

    这不是,因为调用者上下文中没有doubles

    quote do
      doubles = [1,2,3]
      [0,0,0, unquote_splicing(doubles), 0,0,0]
    end
    #⇒ ☠️  ** (CompileError) iex:7: undefined function doubles/0
    

    错误消息说未定义函数,因为 尝试了一个局部变量,如果它在当前上下文中找不到它,它会尝试使用这个名称和arity 调用函数零。

    【讨论】:

      【解决方案2】:

      doublesquote 块内定义时,无需到达quote 块之外来检索doubles 的值。在引用块中定义的变量会自动将它们的值嵌入到 AST 中。因此,可以使用List.flatten()函数:

      defmodule A do
        defmacro double_wrap(nums) do
          quote do
            doubles = Enum.map(unquote(nums), & &1*2)
      
            List.flatten [0,0,0, doubles, 0,0,0]
          end
        end
      end
      

      在 iex 中:

      ~/elixir_programs$ iex
      Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
      Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)
      
      iex(1)> c "a.ex"
      [A]
      
      iex(2)> require A
      A
      
      iex(3)> A.double_wrap [1, 2, 3]
      [0, 0, 0, 2, 4, 6, 0, 0, 0]
      
      iex(4)> 
      

      【讨论】:

      • 这只是一个例子。如果我想将列表作为函数some_fun(:first, unquote_splicing(doubles), :last) 的参数展开或将它们展开为元组而不是列表怎么办? {0, unquote_splicing(something), 0}。对于已修改或已取消引用的值,我将如何处理?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-29
      • 2017-06-05
      • 1970-01-01
      相关资源
      最近更新 更多