Manipulating code tuple inside a macro

I’m not looking for an answer for what I’m trying to do. Instead, I want to know why I’m getting an error.

I’m trying to return a def from a macro, but I want to change the name of the def in the code tuple before returning the code tuple from the macro–but I’m getting an error. Here’s my code:

defmodule My do
  defmacro times_n(n) do

    quote do
      def times(x) do
        x * unquote(n)
      end
    end
    |> IO.inspect
    |> replace_func_name(n)
    |> IO.inspect 

  end

  def replace_func_name(
    {
       :def, 
       context, 
       [
         {func_name, context_import, x}, 
         sublist
       ]
    },
    n
  ) do

    {
      :def, 
      context, 
      [
        {:"#{func_name}_#{n}", context_import, x},
        sublist
      ]
    }

  end 

end

defmodule Test do
  require My
  My.times_n(5)
end

IO.puts My.times_5(3)

Here’s the output I see:

~/elixir_programs$ iex macros2.ex 
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

    {:def, [context: My, import: Kernel],
     [
       {:times, [context: My], [{:x, [], My}]},
       [do: {:*, [context: My, import: Kernel], [{:x, [], My}, 5]}]
     ]}

    {:def, [context: My, import: Kernel],
     [
       {:times_5, [context: My], [{:x, [], My}]},
       [do: {:*, [context: My, import: Kernel], [{:x, [], My}, 5]}]
     ]}

    ** (UndefinedFunctionError) function My.times_5/1 is undefined or private. Did you mean one of:

          * times_n/1

        My.times_5(3)
        macros2.ex:39: (file)
        (elixir) lib/code.ex:677: Code.require_file/2

It looks like I’ve correctly changed the name of the function in the code tuple, but I still get the undefined function error for My.times_5(). Can someone tell me why that is?

You are calling the macro My.times_n/1 while defining the Test module, so the AST returned by that macro is injected into Test.

Try Test.times_5(3) instead.

Heck! Thanks!

Okay, why doesn’t this work:

defmodule Test do
  require My
  My.times_n(5)
  IO.puts times_5(3)   # line 36
end

** (CompileError) macros2.ex:36: undefined function times_5/1

Because when you try to call that function, the module does not yet exist.

You can’t use something from the same context you are still defining.

You are right again! Proof:

defmodule My do
  def go, do: IO.puts "hello"
  go()
end

$ iex my.exs

Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

** (CompileError) my.exs:3: undefined function go/0

Thanks!

And now for the easy way (I finally figured out the syntax!):

defmodule My do
  defmacro times_n(n) do
    quote do
      def unquote(:"times_#{n}")(x), do: x * unquote(n)
    end
    #|> IO.inspect
  end
end

defmodule Test do
  require My
  My.times_n(5)
end

IO.puts Test.times_5(3)
~/elixir_programs$ iex macros2.ex

Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

15
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

Does anyone know why this doesn’t work:

quote do
  def :"times_#{ unquote(n) }"(x), do: x * unquote(n)
end

** (SyntaxError) macros2.ex:4: syntax error before: ‘(’
(elixir) lib/code.ex:677: Code.require_file/2

For the same reason why def :foo(x), do: x does not work. The name of a funtion is not an atom-literal.

Well, I was trying to figure that one out, and based on this working version:

defmodule My do
  defmacro times_n(n) do
    quote do
      def unquote(:"times_#{n}")(x), do: x * unquote(n)
    end
    #|> IO.inspect
  end
end

defmodule Test do
  require My
  My.times_n(5)
end

IO.puts Test.times_5(3)

…it seemed that a function name could be an atom.

Yes, function names are atoms, but atom-literals are not allowed there.

The following being the atom-literal?

def :"times_#{ unquote(n) }"(x)

While this line:

def unquote(:"times_#{n}")(x)

injectes an atom into the function’s AST?