Continuing the Binding and unquote fragments example from the Elixir documentation...
We have a macro that defines functions based on a Keyword list.
defmodule MacroFun do
defmacro defkv(kv) do
quote bind_quoted: [kv: kv] do
Enum.each kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end
end
end
defmodule Runner do
require MacroFun
kv = [foo: 1, bar: 2]
MacroFun.defkv(kv)
end
Runner.foo
Now let's move the body of the macro to a helper function.
defmacro defkv(kv) do
_defkv(kv)
end
defp _defkv(kv) do
quote bind_quoted: [kv: kv] do
Enum.each kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end
end
Great, everything still works. But now what if we want to make another macro that modifies kv
before passing it to the private helper function:
defmacro def_modified_kv(kv) do
quote bind_quoted: [kv: kv] do
modified_kv = Enum.map kv, fn {k, v} -> {k, v + 1} end
_defkv(modified_kv)
end
end
That doesn't work. Elixir says _devkv
is not defined. We can fix by using a fully qualified function name:
defmacro def_modified_kv(kv) do
quote bind_quoted: [kv: kv] do
modified_kv = Enum.map kv, fn {k, v} -> {k, v + 1} end
MacroFun._defkv(modified_kv)
end
end
But then Elixir complains that MacroFun._defkv
is private. So we change it to public, but it still doesn't work because the helper method _devkv
returns quoted code to our macro def_modified_kv
which itself is quoted!
So we can fix that by eval'ing the code returned by the helper function (final code):
defmodule MacroFun do
defmacro defkv(kv) do
_defkv(kv)
end
defmacro def_modified_kv(kv) do
quote bind_quoted: [kv: kv] do
modified_kv = Enum.map kv, fn {k, v} -> {k, v + 1} end
MacroFun._defkv(modified_kv) |> Code.eval_quoted([], __ENV__) |> elem(0)
end
end
def _defkv(kv) do
quote bind_quoted: [kv: kv] do
Enum.each kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end
end
end
- Why did I have to change to calling the helper function by its fully qualified name?
- Why did I have to change the helper function to be public (from private)?
- Is there a better way to do this besides calling
Code.eval_quoted
?
I feel like I'm doing something wrong.
Thanks for the help.