undefined function生成函数



我试图在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逻辑是否正确?你知道我哪里做错了吗?

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

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

假设我正确理解了需求,您希望将函数的编译为静态的。

你的第一个例子几乎是这样做的,除了它没有扩展块本身,因为Kernel.def/2宏是如何扩展的。


你应该理解的是elixir编译器是如何工作的。它首先展开所有的宏,直到没有东西可以展开为止。然后编译生成的AST。def/2返回一个引号表达式,因此没有更多的扩展,整个块的AST被编译。块内容是一个运行时野兽,这使得从那里调用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

如果你的例子是人为的,实际上在中有更复杂的东西,并且你只希望它的某些部分被预编译,在这里你应该清楚地看到为什么编译器不能执行你真正想要的:一个人应该提前准备(读:编译)所有的静态部分。

如前所述,

宏是在编译阶段展开的。也就是说,为了方便,您可以在这里使用宏。

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的踪迹。将注入生成的AST(二进制"you have 42 chances")来代替对func/1的调用。

最新更新