I’m currently writing a specie of DSL and I’m trying to understand how can I escape an anonymous function in a module attribute. I’m not sure how it can be achieved and even tried without success to understand how Absinthe does it with the resolve
macro (e.g. field :name, :string, resolve: _, _ -> ... end
[Absinthe.Type.Field — absinthe v1.7.6]). This is what I have tried so far:
defmodule DSL do
defmacro __using__(_opts) do
quote do
import DSL, only: :macros
Module.register_attribute(__MODULE__, :columns, accumulate: true)
@before_compile DSL
end
end
defmacro __before_compile__(_env) do
quote do
def __columns__, do: Enum.reverse(@columns)
end
end
defmacro column(field, resolver) do
quote do
@columns {unquote(field), unquote(resolver)}
end
end
end
defmodule User do
use DSL
defstruct ~w[first_name last_name email]a
column :name, fn user -> "#{user.first_name} #{user.last_name}" end
column :email, fn user -> user.email end
def test do
user = %__MODULE__{first_name: "John", last_name: "Silva"}
columns = __MODULE__.__columns__()
then(user, columns[:name])
end
end
which fails with:
** (ArgumentError) cannot inject attribute @columns into function/macro because cannot escape #Function<1.54200728/1 in :elixir_compiler_0.__MODULE__/1>. The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, PIDs and remote functions in the format &Mod.fun/arity
(elixir 1.16.0) lib/kernel.ex:3648: Kernel.do_at/5
(elixir 1.16.0) expanding macro: Kernel.@/1
/home/wigny/workspace/dsl.exs:25: User.__columns__/0
/home/wigny/workspace/dsl.exs:25: DSL.__before_compile__/1
I’m aware I could simply solve it by replacing the anonymous function call with a capture
d function call, but I would like to understand how libraries like Absinthe solve this problem…
and Happy New Year to everybody :).