为什么我的动态定义函数未定义?

时间:2017-02-02 00:38:12

标签: elixir

我正在尝试在模块的__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 documentationModule documentation on compile callbacksDomain Specific Languages documentation中提供的示例和a very related question here in SO,但我似乎无法做到这一点。

在此先感谢,任何帮助都将受到高度赞赏!

1 个答案:

答案 0 :(得分:2)

这里有两个错误:

  1. 您需要返回引用的AST。 Enum.each忽略函数返回的值。您需要改为使用Enum.map

  2. 您错过了此行unquote()周围的name

    IO.puts("Hello from function_for_#{name}!")
    
  3. 最终代码:

    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)等类似位置执行此操作。