2

I'm trying to dynamically define some functions within the __using__/1 macro of a module. These functions will have their names defined by the opts parameter provided to the __using__/1 macro. Something like this:

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

Then, this module will be use'd by another module, like this:

defmodule UserModule do
  use MyModule, names: [:foo, :bar]
end

The behavior I would expect is:

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.

But, instead, none of them are defined:

iex> UserModule.function_for_foo(:hello_world)
> ** (UndefinedFunctionError) function UserModule.function_for_foo/1 is undefined or private

I've read some related documents for the last couple of days like: the Kernel.use/2 documentation, the Module documentation on compile callbacks, an example provided in the Domain Specific Languages documentation, and a very related question here in SO, but I just can't seem to make this work.

Thanks in advance, any help will be highly appreciated!

1
  • A best practice suggested in the Kernel documentation suggest that there should be no functions defined in __using__, then maybe my whole approach is wrong? Commented Feb 2, 2017 at 0:38

1 Answer 1

4

There are two mistakes here:

  1. You need to return the quoted AST. Enum.each ignores the value returned by the function. You need to use Enum.map instead.

  2. You missed an unquote() around name in this line:

    IO.puts("Hello from function_for_#{name}!")
    

Final code:

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)

Output:

Hello from function_for_foo!
With parameter: hello_world.
Hello from function_for_bar!
With parameter: hola_mundo.

A best practice suggested in the Kernel documentation suggest that there should be no functions defined in using, then maybe my whole approach is wrong?

That's only when it's possible to define the functions in a module and just import it in use. Since you want to dynamically define the function names and bodies, you need to do that in __using__ (or similar places like @before_compile).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.