lapinkoira
lapinkoira

Reputation: 8998

Define and use functions inside macros

Following this example:

defmodule Greetify do

  defmacro __using__(_) do
    quote do
      Module.register_attribute __MODULE__, :greet, accumulate: true,
        persist: false
      @before_compile Greetify
    end
  end

  defmacro __before_compile__(env) do
    greetings = Module.get_attribute(env.module, :greet)
    for {name, age} <- greetings do
      IO.puts "#{name} is #{age} years old"
    end
  end

end

Can it be possible to define internal functions to use within the macro?

For example:

  defmacro __before_compile__(env) do
    greetings = Module.get_attribute(env.module, :greet)
    say_greetings(greetings)

    defp say_greetings(grettings) do
      for {name, age} <- greetings do
        IO.puts "#{name} is #{age} years old"
      end
    end
  end

Trying this the compiler complais with function say_grettings is not defined

That example source http://elixir-recipes.github.io/metaprogramming/accumulating-annotations/

Upvotes: 0

Views: 2226

Answers (1)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

Well, this is possible. The issue with your code is that you mix the scopes. Macros in Elixir are being expanded during the compilation stage. There is no compiled say_greetings/1 function at this very moment (besides that one cannot invoke defp from inside defmacro, but this might be overcame with a proper quoting.)

What you need for this to work, would be to declare say_greetings/1 in the same scope as __before_compile__/1 to make is available for it. You cannot define it as a function (see above,) but the workaround would be to define it as a macro as well. That way it will be expanded during a compilation and everything will work (also, I doubt I understand what is the reason for that.)

The summing up:

defmodule Greetify do    
  defmacro __using__(_) do
    quote do
      Module.register_attribute __MODULE__, :greet, accumulate: true, persist: false
      @before_compile Greetify
    end
  end

  defmacrop say_greetings(greetings) do
    quote do
      for {name, age} <- unquote(greetings) do
        IO.puts "#{name} is #{age} years old"
      end
    end
  end

  defmacro __before_compile__(env) do
    greetings = Module.get_attribute(env.module, :greet)
    say_greetings(greetings)
  end
end

defmodule Test do
  use Greetify

  @greet {"Jon", 21}
  @greet {"Sam", 23}
end

Upvotes: 2

Related Questions