Macro and :erlang.atom_to_binary error

Hi, I’ve got macro which returns function
@spec my_macro(atom()) :: fun()
defmacro my_macro(var1) do

end

In Repl when I invoke :

my_macro(:my_atom) ,
the result is what I expected : #Function<13.126501267/2 in :erl_eval.expr/5>

but when I invoke

my_atom_2 = :my_atom
my_macro(my_atom_2)

I’ve got error :
** (ArgumentError) argument error
:erlang.atom_to_binary({:my_atom_2, [line: 36], nil}, :utf8)

How to solve it ? I need pass variable to macro not atom explicit value

Thanks for help in advance .
TT

This isn’t how macros work. Macros don’t work on values, they work on AST. Can you elaborate on how your overall goal? We can’t suggest a solution without understanding the problem.

Macros can’t return something of type fun. They have to return AST, you can build the AST by hand or by using quote.

Can you explain what you are trying to do?

Perhaps even show some code?

Hi, I try to invoke function from module name depends on variable passing to macro (variable must be an atom) .

@base_module = My.Base.Module
@function_name = :my_function
 defmacro my_macro(my_atom) do 
  
    subModule =
       my_atom |> Atom.to_string |> String.capitalize |> String.to_atom 

    quote do 
      &(Module.concat(unquote(@base_module), unquote(subModule))).unquote(@function_name)/2
    end
    
  end
end

then :

my_macro(:my_atom) ,

the result is ok: #Function<13.126501267/2 in :erl_eval.expr/5>

but :

my_atom_2 = :my_atom
my_macro(my_atom_2)

I’ve got error :

** (ArgumentError) argument error
:erlang.atom_to_binary({:my_atom_2, [line: 36], nil}, :utf8)

Regards
TT

Since macros work on AST, not on values, the macro cannot know what the value of my_atom2 is at compile time. Anything outside the quote block is compile time.

The reason your code work on when you used :my_atom is that an atom’s AST is itself and so could be resolved at compile time.

Therefore resolving the function name needs to move to be runtime, ie inside the quote block like this:

defmodule M do
  @base_module My.Base.Module
  @function_name :my_function
  defmacro my_macro(my_atom) do
    quote do
      sub_module =
        unquote(my_atom) |> Atom.to_string |> String.capitalize |> String.to_atom

      &(Module.concat(unquote(@base_module), sub_module)).unquote(@function_name)/2
    end
  end
end
1 Like

Thank you very much for explanation, now works .

Why don’t you just use a function which calls apply/*?

I didn’t know that the function “apply” exists :slight_smile: , thanks for hint .
Regards