Passing computed list to an Elixir macro

I have a map that I want to use a single source of truth for a couple of functions. Let’s say it is:

source_of_truth = %{a: 10, b: 20}

I’d like the keys of that map to be values of EctoEnum. EctoEnum provides a macro defenum that I should use like this:

  defenum(
    EnumModule,
    :enum_name,
    [:a, :b]
  )

I don’t want to repeat [:a, :b] part. I’d like to use the keys from the map instead like this:

  defenum(
    EnumModule,
    :enum_name,
    Map.keys(source_of_truth)
  )

It doesn’t work because defenum macro expects a plain list.

I thought I could do it by defining my own macro like this:

 defmacro dynamic_enum(enum_module, enum_name, enum_values) do
   quote do
     defenum(
       unquote(enum_module),
       unquote(enum_name),
       unquote(enum_values)
     )
   end
 end

and then call:

dynamic_enum(EnumModule, :enum_name, Map.keys(source_of_truth))

However, it doest the same thing: enum_values is not a precomputed list but AST for Map.get. My next approach was:

 defmacro dynamic_enum(enum_module, enum_name, enum_values) do
   quote do
     values = unquote(enum_values)
     defenum(
       unquote(enum_module),
       unquote(enum_name),
       ?
     )
   end
 end

Not sure what I could put where the ? is. I can’t just put values because it is a variable and not a list. I can’t put unquote(values) either.

A solution that sort of works is this one:

defmacro dynamic_enum(enum_module, enum_name, enum_values) do
  {values, _} = Code.eval_quoted(enum_values)
  quote do
    defenum(
      unquote(enum_module),
      unquote(enum_name),
      unquote(values)
    )
  end
end

However, the docs say that using eval_quoted inside a macro is a bad practice.

What is a good solution for that problem?

Also posted on SO: https://stackoverflow.com/questions/53651520/passing-computed-list-to-an-elixir-macro

Does not this work?

  defenum(
    EnumModule,
    :enum_name,
    unquote(Map.keys(source_of_truth))
  )

EDIT: Although that depends on in what scope it is run in, if top level then no, eval’ing it there would be best.

Unfortunately that doesn’t work and that is the main issue:

** (Protocol.UndefinedError) protocol Enumerable not implemented for {:unquote, ...
Since defenum is a macro, it passes its last argument as an AST and the definition expects a literal list. That is why I thought it might be possible to write a macro that first evaluates the third argument and then passes a literal list to defenum.

Yeah so need to wrap it up in an eval then at the scope it is being run in.

I got an excellent answer on SO

Thanks guys for your help!