43

I want to create a function names dynamically. I wrote this macro

defmacro generate_dynamic(name) do
  quote do 
    def add_(unquote(name)) do
    end
  end
end

And I used it like this:

defmodule AnotherModule do
  generate_dynamic :animal
end

Now, I only get AnotherModule.add_ function defined, whereas I expect AnotherModule.add_animal function.

3 Answers 3

57

To achieve this, you can prepend :add_ to the name before unquoting. Also, the parentheses after the method name in this case are required to prevent ambiguities. This should do the trick:

defmacro generate_dynamic(name) do
  quote do 
    def unquote(:"add_#{name}")() do
      # ...
    end
  end
end
Sign up to request clarification or add additional context in comments.

1 Comment

Normally when defining functions without arguments, the parentheses are omitted, but when defining them with macros, the parentheses are always necessary.
30

Sometimes, as a useful shortcut, you can achieve the same result inline, without writing a macro using an unquote fragment.

defmodule Hello do
  [:alice, :bob] |> Enum.each fn name ->
    def unquote(:"hello_#{name}")() do
      IO.inspect("Hello #{unquote(name)}")
    end
  end
end

Hello.hello_bob    # => "Hello bob"
Hello.hello_alice  # => "Hello alice"

3 Comments

I thought unquote only worked from inside a macro. How come it's working inside an anonymous function?
I answer myself: unquote and unquote fragments are two different things.
Jusy to clarify the above: unquote fragments are unquotes that are outside of a quote block.
9

I did this same sort of thing in a gist to try and mimic Ruby's attr_accessor:

defmodule MacroExp do
  defmacro attr_accessor(atom) do
    getter = String.to_atom("get_#{atom}")
    setter = String.to_atom("set_#{atom}")
    quote do
      def unquote(getter)(data) do
        data |> Map.from_struct |> Map.get(unquote(atom))
      end
      def unquote(setter)(data, value) do
        data |> Map.put(unquote(atom), value)
      end
    end
  end

  defmacro attr_reader(atom) do
    getter = String.to_atom("get_#{atom}")
    quote do
      def unquote(getter)(data) do
        data |> Map.from_struct |> Map.get(unquote(atom))
      end
    end
  end
end


defmodule Calculation do
  import MacroExp
  defstruct first: nil, second: nil, operator: :plus

  attr_accessor :first   # defines set_first/2 and get_first/1
  attr_accessor :second  # defines set_second/2 and get_second/1
  attr_reader :operator  # defines set_operator/2 and get_operator/1

  def result(%Calculation{first: first, second: second, operator: :plus}) do
    first + second
  end
end

https://gist.github.com/rcdilorenzo/77d7a29737de39f0cd84

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.