I am trying to understand how macros work – specifically how macro/caller contexts work. I stumbled upon these set of codes that have differing behaviors which conflict with my current understanding of these contexts.
(1) In the first code example below, I am attempting to access a caller context variable from a dynamically generated function.
defmacro foo do
quote do
x = 1 + 1
def execute(), do: IO.inspect(unquote(x))
end
end
However, this does not work. It presents the following error:
** (CompileError) ambiguous.exs:14: undefined function x/0
(elixir 1.11.3) src/elixir_locals.erl:114: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3
(stdlib 3.15) erl_eval.erl:685: :erl_eval.do_apply/6
(2) In the second code example, I attempt to do the same thing, except the caller context variable is retrieved differently.
defmacro foo(opts) do
quote bind_quoted: [opts: opts] do
x = Keyword.get(opts, :x)
def execute(), do: IO.inspect(unquote(x))
end
end
I can actually access the caller context variable x
from the dynamically generated function.
From what I can gather, the two code examples above are of similar nature – even if the way the results are reached. Both include the dynamically generated function (execute
) attempting to access a caller context variable (x
) through unquote
. The former assigns this variable through a simple expression while the retrieves this from a keyword list that has been bound to the caller context through bind_quoted
.
(3) Even more interestingly, the following code example works:
defmacro foo(x) do
quote do
def execute(), do: IO.inspect(unquote(x))
end
end
Where the dynamically generated function is able to access a macro context variable.
My question is how do the examples differ, especially the first two code examples? What exactly is happening that allows the second to work but not the first? Why does the third work but the first doesn’t?
Note: I am using this macro in the same way across (1), (2), and (3):
defmodule Applied do
require Confusion # this is the module that contains the macro food
Confusion.foo() # for (1)
Confusion.foo(x: 5) # for (2)
Confusion.foo(5) # for (3)
end
Any help would be great! Thanks!