Elixir, use and using

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