🎓 A1Learner
🔍
0 of 46 lessons completed

Elixir Macros with Using

The __using__ macro runs when another module says use. It inserts shared code into the module that says use, shaped by the options given.

use inserts shared code

TeamB says use with no options. It gets the default greeting.

defmodule GreeterBase do
  defmacro __using__(opts) do
    greeting = Keyword.get(opts, :greeting, "Hello")
    quote do
      def greet(name), do: unquote(greeting) <> ", " <> name
    end
  end
end
defmodule TeamB do
  use GreeterBase
end
IO.puts(TeamB.greet("Bo"))

Options shape the inserted code

TeamA passes a different greeting. Its greet uses that greeting.

defmodule GreeterBase do
  defmacro __using__(opts) do
    greeting = Keyword.get(opts, :greeting, "Hello")
    quote do
      def greet(name), do: unquote(greeting) <> ", " <> name
    end
  end
end
defmodule TeamA do
  use GreeterBase, greeting: "Hi"
end
IO.puts(TeamA.greet("Ana"))

One use can inject several functions

The base injects greet and farewell together.

defmodule GreeterBase do
  defmacro __using__(opts) do
    greeting = Keyword.get(opts, :greeting, "Hello")
    quote do
      def greet(name), do: unquote(greeting) <> ", " <> name
      def farewell(name), do: "Goodbye, " <> name
    end
  end
end
defmodule TeamC do
  use GreeterBase, greeting: "Hey"
end
IO.puts(TeamC.greet("Ana"))
IO.puts(TeamC.farewell("Ana"))

Example

defmodule GreeterBase do
  defmacro __using__(opts) do
    greeting = Keyword.get(opts, :greeting, "Hello")
    quote do
      def greet(name), do: unquote(greeting) <> ", " <> name
      def farewell(name), do: "Goodbye, " <> name
    end
  end
end
defmodule TeamA do
  use GreeterBase, greeting: "Hi"
end
defmodule TeamB do
  use GreeterBase
end
IO.puts(TeamA.greet("Ana"))
IO.puts(TeamA.farewell("Ana"))
IO.puts(TeamB.greet("Bo"))
IO.puts(TeamB.farewell("Bo"))

Key points

  • __using__: __using__ is the macro that runs for a use call.
  • Use: use Module, option: value calls the __using__ macro of that module with those options.
  • Options: Keyword.get reads one option from the options list, with a default when it is missing.
  • Injected code: the code returned by __using__ becomes part of the module that says use.
  • Shared behaviour: one __using__ definition can give many modules the same functions.

In this example, the base defines __using__ with a greeting option, TeamA uses it with Hi, TeamB uses it with the default, and each module prints its own greet and farewell.

Note

use runs once per module, so each module gets its own copy of the injected code.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

What does use GreeterBase, greeting: "Hi" do?