Reputation: 856
I have a macro that defines a module like so.
defmodule Bar do
def bar do
IO.puts "I am #{inspect __MODULE__}"
end
end
defmodule MacroFun do
defmacro define_module(name) do
quote do
defmodule unquote(name) do
import Bar
def foo do
bar
IO.puts "I am #{inspect __MODULE__}"
end
end
end
end
end
defmodule Runner do
require MacroFun
def run do
MacroFun.define_module Foo
Foo.foo
end
end
Runner.run
The output of running this is:
I am Bar
I am Runner.Foo
Which makes sense; MacroFun.define_module
was called in Runner.run
so the module was defined and thus nested under the Runner
module.
But now if I change MacroFun.define_module
to use the :bind_quoted
option:
defmacro define_module(name) do
quote bind_quoted: [name: name] do
defmodule name do
import Bar
def foo do
bar
IO.puts "I am #{inspect __MODULE__}"
end
end
end
end
The output now becomes:
I am Bar
I am Foo
Why??
Upvotes: 9
Views: 2914
Reputation: 121
With this code you will see the correct values used to create the modules:
require Logger
defmodule Bar do
def bar do
IO.puts "I am #{inspect __MODULE__}"
end
end
defmodule MacroFun do
defmacro define_module(name) do
quote do
Logger.debug("#{inspect unquote(name)}")
defmodule unquote(name) do
import Bar
Logger.debug("#{inspect unquote(name)}")
def foo do
bar
IO.puts "I am #{inspect __MODULE__}"
end
end
end
end
defmacro define_module2(name) do
quote bind_quoted: [name: name] do
defmodule name do
import Bar
Logger.debug("#{inspect name}")
def foo do
bar
IO.puts "I am #{inspect __MODULE__}"
end
end
end
end
end
defmodule Runner do
require MacroFun
def run do
MacroFun.define_module Foo
Foo.foo
end
def run2 do
MacroFun.define_module2 Foo2
Foo2.foo
end
end
Runner.run
Runner.run2
Output:
[warn] Foo
[warn] Runner.Foo
I am Bar
I am Runner.Foo
[warn] Foo2
I am Bar
I am Foo2
Upvotes: 0
Reputation: 1591
I think that's because the place where you are unquoting (binding) the variable name
.
In the first case, you are unquoting the variable name
when creating a module, thus binding the variable at that moment would require to check for context (check if the code is inside another module, for example). So, you get your current atom plus the appropriate context: Runner.Foo
.
In the second case, you are unquoting the variable name
before it's placed in a context, therefore its value will not change and it'll be the atom Foo
(no context).
Upvotes: 9