defmodule MyLib do
defmacro __using__(options) do
quote do
# @var1 options[:var1]
# @var2 options[:var2]
@var1 unquote(options[:var1])
@var2 unquote(options[:var2])
end
end
def func1(_a) do
IO.puts("var1 is: #{@var1}")
end
def func2(_a) do
IO.puts("var2 is: #{@var2}")
end
end
And this:
defmodule MyClient do
use MyLib, [var1: 333, var2: 555]
MyLib.func1(nil)
MyLib.func2(nil)
end
The code prints out nil for both variables. It’s because I’ve gotten it vise versa. Namely, they get injected into MyClient instead. Correct?
My goal is to set the module variables in MyLib in order to be able to use them throughout MyLib module. How? Simply passing them to each function as arguments won’t be a good solution because my real code is more complicated. Neither will work nicely utilizing config values either.
Prior to use MyLib, ..., the MyLib module will be compiled and there’s no chance to inject anything into it. Additionally, two other considerations:
Macros accept AST and return AST. They are functions and generally don’t have side effects.
Macro callers don’t change the called context. If thats what you’re after it sounds a little “object oriented” and you’re seeking for some kind of class variable. Module attributes aren’t anything like that.
In summary:
A macro, by definition, inserts code at the called site. So whatever MyLib.using/1 generates is inserted into the calling module, in this case MyClient.
A macro can access the calling environment via the __CALLER__ special variable but thats not necessary here since you are already passing a binding ([var1: 333, ...])
If you are seeking to customise the generated code based upon the parameters to __using__/1 then just extract them from option when you need them in the generated code as you are doing.
The functions in MyLib cannot access the compile-time environment of MyClient - MyLib us compiled before the macro is called and all environment variables have gone so this path is not viable, or even really appropriate - state is not maintained in a module or a function.
In general, the Elixir approach is to inject generated code into the client module and have it call a function in the MyLib module with the given parameter if required. For example something like this:
defmodule MyLib do
defmacro __using__(options) do
quote do
def func1(), do: MyLib.func1(unquote(options[:var1]))
def func2(), do: MyLib.func1(unquote(options[:var2]))
end
end
def func1(var1) do
IO.puts("var1 is: #{var1}")
end
def func2(var2) do
IO.puts("var2 is: #{var2}")
end
end
defmodule MyClient do
use MyLib, [var1: 333, var2: 555]
func1()
func2()
end