I didn’t include much information in my post More modules in Elixer around the use macro and that’s because it really requires a post on it’s own. So here we go…
If you’ve uses Phoenix (partially covered in Elixir and Phoenix) you’ll have probably noticed that when we created the controller we gained access to functions such as json and when we setup the router we had a long list of except: atoms. This is because using use bought functions into the modules from Phoenix automatically, i.e. new, edit, create etc. functions.
The use macro is quite powerful, it essentially calls the __using__ macro within another module. The __using__ macro allows us to inject code from the other module.
Let’s see this in action…
We’ll start by creating a module which will include functionality that can be injected into another module
defmodule MyUse do defmacro __using__(_opts) do quote do def my_injected_fn() do "Hello World" end end end end
In the above example, we create the macro with the my_injected_fn. The neat bit is the __using__ which injects this code into a module which uses the MyUse module, for example
defmodule MyModule do use MyUse def test_use() do my_injected_fn() end end
This will inject all macros, but in the Phoenix example we want to inject only certain pieces from a module.
Before we move on let’s quickly address a couple of things in the code above. The quote macro tranforms the block of code into an AST (Abstract Syntax Tree), we can see an example of this by type the following into iex quote do: MyModule.testuse() and it will display something like {{:., [], [{:__aliases__, [alias: false], [:MyModule]}, :testuse]}, [], []}. It’s probably quite obvious that defmacro defines a macro and the __using__ calback macro is what allows us to extend other modules (as already mentioned).
Let’s change the MyUse module to allow us to inject specific functionality.
defmodule MyUse do defmacro __using__(which) when is_atom(which) do apply(__MODULE__, which, []) end def injector do quote do def my_injected_fn() do "Hello World" end end end end
We’re now using the macro __using__ to select which bits of functionality we want to inject into another module. Admittedly in this example we just have a single piece of code to be injected, but bare with me.
So now to use this in our other modules we write the following
defmodule MyModule do use MyUse, :injector def test_use() do my_injected_fn() end end
This will inject the injector defined code.
Let’s be honest this is not that useful with one function, so let’s extend the MyUse with a couple of functions (in my case, just to demonstrate things I’ve given them the same name but in most modules you’ll probably not be doing this
defmodule MyUse do defmacro __using__(which) when is_atom(which) do apply(__MODULE__, which, []) end def injector1 do quote do def my_injected_fn() do "Hello World 1" end end end def injector2 do quote do def my_injected_fn() do "Hello World 2" end end end end
What we’re going to do, is in the calling module, we can select injector1 OR injector2 an without changing the calling function name (as it’s unchanged in the MyUse module)
defmodule MyModule do use MyUse, :injector1 def test_use() do my_injected_fn() end end
This will display “Hello World1” when evaluated. Switching the :injector2 will display “Hello World 2.
As mentioned it’s unlikely you’ve normally do this, it’s much more likely you might include different functions, maybe along these lines
defmodule MyUse do defmacro __using__(which) when is_atom(which) do apply(__MODULE__, which, []) end def injector1 do quote do def hello1() do "Hello World 1" end end end def injector2 do quote do def hello2() do "Hello World 2" end end end end
Now we can inject these via use, like this
defmodule MyModule do use MyUse, :injector1 use MyUse, :injector2 def test_use() do IO.puts hello1() IO.puts hello2() end end
Note: I’m still quite new to Elixir, the usage of two use clauses seems a little odd, there may be a better way to define such things.
I mentioned you could use import in much the same way, except use allows us to inject aliases, imports other use modules etc.
References
The ‘use’ Macro in Elixir.
Understanding Elixir’s Macros by Phoenix example
How to Use Macros in Elixir
Phoenix repo on GitHub