【问题标题】:undefined function Generating Functions未定义函数生成函数
【发布时间】:2021-10-06 05:13:45
【问题描述】:

我正在尝试使用 Elixir 中的元编程生成 100 个函数

我希望函数返回一个在编译时计算的值

def func(0) do
  "you have 0 chances"
end

def func(1) do
  "you have 1 chances"
end

...

def func(100) do
  "you have 100 chances"
end

我的第一次尝试是

0..100 |> Enum.each fn val ->
    def func(unquote(val)) do
        val_string = to_string(unquote(val))
        "you have " <> val_string <> " chances"
    end
end

但我有理由相信这只会返回 100 个在编译时未评估的函数。

我终于试过了

0..100 |> Enum.each fn vol ->
  defmacro func(unquote(vol) = vol) do
    quote do
      "you have " <>  unquote(vol) <> " chances" |> unquote
    end
  end
end

但是当我需要该文件并在 iex 中调用 func(1) 时,我得到了

** (CompileError) iex:2: undefined function func/1

首先我的 defmacro 逻辑是否正确?知道我可能做错了什么。

【问题讨论】:

    标签: elixir metaprogramming


    【解决方案1】:

    假设我正确理解了这个要求,你希望函数的 block 被编译成有点静态。

    您的第一个示例几乎就是这样做的,除了它不会扩展块本身,因为 Kernel.def/2expands 的方式。


    您应该了解的是, 编译器实际上是如何工作的。它从深度扩展所有宏开始,直到没有什么可扩展的。然后生成的 AST 被编译。 def/2 返回一个带引号的表达式,因此没有更多的扩展,整个块的 AST 被编译。块内容是运行时的野兽,这使得 e. G。从那里调用System.get_env/1 并检索运行时环境。

    如果您的示例不是人为设计的,您可能会在编译阶段对块内容进行显式预编译:

    0..100 |> Enum.each fn val ->
      val_string = to_string(val)
      block = "you have " <> val_string <> " chances"
      def func(unquote(val)), do: unquote(block)
    end
    

    如果您的示例是人为设计的,而实际上 block 中有更复杂的内容,并且您希望只预编译其中的某些部分,那么您应该清楚地了解编译器为何无法预编译的原因执行您真正想要的:应该预先准备(阅读:编译)所有静态部分。


    正如我上面所说的,宏是在编译阶段扩展的。也就是说,为方便起见,您可以在那里使用宏。

    0..100 |> Enum.each fn val ->
      defmacro func(unquote(val) = val) do
        quote bind_quoted: [val: val] do
          "you have #{val} chances"
        end
      end
    end
    

    那么在生成的代码中将不会有任何func 的踪迹。代替对func/1 的调用,将注入生成的AST(二进制"you have 42 chances")。

    【讨论】:

      【解决方案2】:

      首先,以防万一,您可以使用常规函数实现相同的目标,而无需元编程:

      def func(num) when num in 0..100 do
        "you have #{num} chances"
      end
      

      但如果您正在练习元编程,请记住您需要 unquote 任何编译时值:

      for i <- 0..100 do
        def func(unquote(i)) do
          unquote("you have #{i} chances")
        end
      end
      

      【讨论】:

      • 在第二个示例中,可以使用unquote("You have #{i} chances") 在编译时而不是运行时生成字符串。在第一个示例中,when num in 0..100 恕我直言更清楚。
      • 感谢@Hauleth,有趣的范围甚至可以编译到同一个守卫:is_integer(num) and (num &gt;= 0 and num &lt;= 100)。同意它更清楚并更新了两种情况的答案。
      • 我相信您稍微误解了要求,并且问题中的代码示例是人为的。 AFAIU,问题是“如何在编译时评估块内容”。请看我的回答。
      • 再想一想,num in 0..100 确实要求您知道范围是离散的,即使用像 5.5 in 0..100 这样的非整数是 false。写很长的路是明确的,并且需要较少的关于范围如何工作的知识。因此,IMO 更清楚的是不是一目了然。至于要求是什么,我想除非OP回来,否则我们不会知道。
      猜你喜欢
      • 1970-01-01
      • 2018-12-20
      • 1970-01-01
      • 2017-01-24
      • 1970-01-01
      • 1970-01-01
      • 2021-10-16
      • 2011-09-27
      • 1970-01-01
      相关资源
      最近更新 更多