我正在尝试在模块的__using__/1
宏中动态定义一些函数。这些函数的名称由提供给opts
宏的__using__/1
参数定义。像这样:
defmodule MyModule do
defmacro __using__(opts) do
names = Keyword.get(opts, :names)
Enum.each(names, fn(name) ->
quote bind_quoted: [name: name] do
def unquote(:"function_for_#{name}")(param) do
IO.puts("Hello from function_for_#{name}!")
IO.puts("With parameter: #{param}.")
end
end
end)
end
end
然后,此模块将由另一个模块use
组成,如下所示:
defmodule UserModule do
use MyModule, names: [:foo, :bar]
end
我期望的行为是:
iex> UserModule.function_for_foo(:hello_world)
> Hello from function_for_foo!
> With parameter: :hello_world.
iex> UserModule.function_for_bar(:hola_mundo)
> Hello from function_for_bar!
> With parameter: :hola_mundo.
但是,相反,它们都没有定义:
iex> UserModule.function_for_foo(:hello_world)
> ** (UndefinedFunctionError) function UserModule.function_for_foo/1 is undefined or private
我在过去几天阅读了一些相关文档,例如:Kernel.use/2 documentation,Module documentation on compile callbacks,Domain Specific Languages documentation中提供的示例和a very related question here in SO,但我似乎无法做到这一点。
在此先感谢,任何帮助都将受到高度赞赏!
答案 0 :(得分:2)
这里有两个错误:
您需要返回引用的AST。 Enum.each
忽略函数返回的值。您需要改为使用Enum.map
。
您错过了此行unquote()
周围的name
:
IO.puts("Hello from function_for_#{name}!")
最终代码:
defmodule MyModule do
defmacro __using__(opts) do
names = Keyword.get(opts, :names)
Enum.map(names, fn(name) ->
quote bind_quoted: [name: name] do
def unquote(:"function_for_#{name}")(param) do
IO.puts("Hello from function_for_#{unquote(name)}!")
IO.puts("With parameter: #{param}.")
end
end
end)
end
end
defmodule UserModule do
use MyModule, names: [:foo, :bar]
end
UserModule.function_for_foo(:hello_world)
UserModule.function_for_bar(:hola_mundo)
输出:
Hello from function_for_foo!
With parameter: hello_world.
Hello from function_for_bar!
With parameter: hola_mundo.
内核文档中建议的最佳实践表明,使用中不应定义任何功能,那么我的整个方法可能是错误的?
只有当可以在模块中定义函数并将其导入use
时才会这样。由于您要动态定义函数名称和正文,因此需要在__using__
(或@before_compile
)等类似位置执行此操作。