Passing a list of maps into a macro

Hey Alchemists,

I am attempting to pass in a list of maps into a macro so that it can be used to generate an absinthe object for my schema. I have tried several approaches to inject the fields into the macro: var!, Macro.expand, Macro.escape but I still haven’t untangled this problem. I would have expected the expansion to allow me to use the fields in the unquote block. Below is my spike macro and it’s invocation. What am I failing to understand?

defmodule Foo do
  defmacro do_it(fields) do
    quote do
      object :item do
        unquote do
          fields
           |> Enum.map(fn f ->
            name = f |> Map.get(:field)
            type = f |> Map.get(:type)
            quote do: field(unquote(name), unquote(type))
          end)
        end
      end
    end
  end

  def fields do
  [
      %{field: :warble, type: :id},
      %{field: :wazzle, type: :string},
      %{field: :tex, type: :integer}
    ]
  end
end

# Invocation
require Foo
Foo.do_it(Foo.fields()) 

# Compile error Error 
== Compilation error in file lib/wibble_web/schema.ex ==
** (Protocol.UndefinedError) protocol Enumerable not implemented for {{:., [line: 29], [{:__aliases__, [line: 29], [:Foo]}, :fields]}, [line: 29], []}
    (elixir) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir) lib/enum.ex:141: Enumerable.reduce/3
    (elixir) lib/enum.ex:3015: Enum.map/2
    expanding macro: Foo.do_it/1
    lib/wibble_web/schema.ex:29: WibbleWeb.Schema (module)
    (elixir) lib/kernel/parallel_compiler.ex:208: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/6

Your do_it macro is receiving the ast for ‘Foo.fields’ itself not the evaluation of Foo.fields which would be the list.

1 Like

Hey @steven-solomon. The short version is that macros receive AST, not values. Foo.do_it is a macro, and so it gets the AST of its arguments, and the AST is a function call.

The long version is that trying to manipulate Absinthe schemas this way is no longer necessary in 1.5. As of 1.5 there are callbacks within a schema that let you use simple and ordinary Elixir datastructures to dynamically add content to schemas. I will try to reply here later today with examples.

3 Likes

Is it not possible to expand it into the actual value?

Expanding is not the same thing as evaluating. Expansion refers to letting any other macros also run and return AST. Evaluation is something different entirely, and will always happen after the macros have all expanded.

If you want something to be run that you’re passing to a macro, the macro needs to return AST that says what to do with the value, it will never get the value itself.

1 Like

That makes sense. I created the data structure in the macro in order to not have it expand into the AST version. I’ll post an example this afternoon.