Alex Antonov
Alex Antonov

Reputation: 15226

How to define functions based on attribute to elixir?

Let's say I have a modules Silent and Definer. I want to define a couple of functions for Silent, based on its attribute. Let me explain:

defmodule Silent do
  @function_names [:a, :b, :c]

  use Definer
end

defmodule Definer do
  defmacro __using__(_) do
    quote do
      Enum.each(@function_names, fn(n) ->
        def unquote(n)() do # line 5
          IO.puts "a new method is here!"
        end
      end)
    end
  end
end

But this approach actually doesn't work because I have undefined function n/0 on line 5. How can I implement desired functionality?

Upvotes: 2

Views: 266

Answers (1)

Dogbert
Dogbert

Reputation: 222428

You need to pass unquote: false to quote in Definer.__using__/1 to be able to inject an unquote fragment inside a quote.

defmodule Definer do
  defmacro __using__(_) do
    quote unquote: false do
      Enum.each(@function_names, fn(n) ->
        def unquote(n)() do # line 5
          IO.puts "a new method is here!"
        end
      end)
    end
  end
end

defmodule Silent do
  @function_names [:a, :b, :c]

  use Definer
end

Silent.a
Silent.b
Silent.c

prints

a new method is here!
a new method is here!
a new method is here!

A similar case is documented in detail in the Kernel.SpecialForms.quote/2 docs which also mentions how to use bind_quoted if you want to both inject some variables into a quote and create unquote fragments.

Upvotes: 1

Related Questions