编译阶段
我不明白genFB/2 是如何被编译器未定义或看不到的。
关于Elixir,人们应该清楚了解的主要一点:与许多其他语言不同,它对“元编程”和代码本身使用相同的语法。作为在 VM 中运行的编译语言,Elixir 无法执行任意代码。 代码必须预先编译。
编译基本上意味着将代码转换为AST然后转换为BEAM。
在最顶层范围(以及defmodule 宏内部)中找到的代码正在编译阶段执行。 它不包含在生成的 BEAM 中。考虑以下示例:
defmodule Test do
IO.puts "?️ Compilation stage!"
def yo, do: IO.puts "⚡ Runtime!"
end
Test.yo
如果您尝试编译它,您将看到"?️ Compilation stage!" 仅在编译期间打印。无法从生成的 BEAM 中引用此代码,因为它在编译期间执行后被简单地丢弃。
OTOH,要打印 "⚡ Runtime!" 字符串,您需要在运行时显式运行 Test.yo。
也就是说,您的 doN 变量(即使它们引用有效的又名可用/已知的编译器函数)在编译阶段被分配为局部变量并立即丢弃,因为没有人使用它们.
解决方法 1
在运行时函数中有一些可用的东西:
模块属性
它们之所以可用,是因为编译器在看到模块属性和/或宏时,将生成的 AST 就地注入,而不涉及它。考虑以下示例:
defmodule Test do
@mod_attr &IO.puts/1
def yo, do: @mod_attr.("⚡ Runtime!")
end
Test.yo
这里我们声明了模块属性,引用了函数IO.puts/1并从运行时调用它。
我们引用的函数必须在它被引用的时候编译。
宏
考虑以下示例。
defmodule Test do
defmacrop puts(what), do: IO.puts(what)
def yo, do: puts("?️ Compilation time!")
end
等等,什么? 它是在编译阶段打印出来的!是的。宏注入由它们的do: 块产生的AST,因此IO.puts(what)在编译阶段被执行。
要解决这个问题,应该quote 宏的内容,按原样注入而不是执行它。
defmodule Test do
defmacrop puts(what), do: quote(do: IO.puts(unquote(what)))
def yo, do: puts("⚡ Runtime!")
end
Test.yo
因此,您可能已经通过引入一个繁琐的宏、注入对真实函数的调用来修复您的代码,但我会将其排除在此答案的范围之外。完成任务还有更简单的方法。
解决方法 2
defmodule FizzBuzz.Fb4a do
def upto(n) when n > 0, do: fizzbuzz(n)
defp toTuple(n), do: {n, ""}
defp toString({v, ""}), do: v
defp toString({_v, a}), do: a
defp genFB({v, a}, d, s) when rem(v, d) == 0, do: {v, a <> s}
defp genFB({v, a}, _d, _s), do: {v, a}
defp fizzbuzz(n) do
1..n
|> Enum.map(&toTuple/1)
|> Enum.map(&genFB(&1, 3, "Fizz"))
|> Enum.map(&genFB(&1, 5, "Bazz"))
|> Enum.map(&genFB(&1, 7, "Bang"))
|> Enum.map(&toString/1)
end
end
我已经稍微清理了代码以使用模式匹配而不是命令式if 和cond 子句。
首先,您不需要返回函数。 Elixir 有一个漂亮的功能,允许使用& 捕获函数。您甚至可以将 curried 函数的结果分配给一个变量并稍后调用它,但这里并不真正需要。
如果你还想分配中间变量,请确保函数所属的模块已经编译。
defmodule FizzBuzz.Fb4a do
defmodule Gen do
def genFB({v, a}, d, s) when rem(v, d) == 0, do: {v, a <> s}
def genFB({v, a}, _d, _s), do: {v, a}
end
def upto(n) when n > 0, do: fizzbuzz(n)
defp toTuple(n), do: {n, ""}
defp toString({v, ""}), do: v
defp toString({_v, a}), do: a
defmacrop do3, do: quote(do: &Gen.genFB(&1, 3, "Fizz"))
defmacrop do5, do: quote(do: &Gen.genFB(&1, 5, "Bazz"))
defmacrop do7, do: quote(do: &Gen.genFB(&1, 7, "Bang"))
defp fizzbuzz(n) do
1..n
|> Enum.map(&toTuple/1)
|> Enum.map(do3())
|> Enum.map(do5())
|> Enum.map(do7())
|> Enum.map(&toString/1)
end
end
希望这会有所帮助。