use and __using__
Learn about use and __using__ macros in Elixir.
We'll cover the following
Introduction
In one sense, use
is a trivial function. We pass it a module along with an optional argument, and it invokes the function or macro __using__
in that module, passing it the argument.
Yet this simple interface gives us a powerful extension facility. For example, in our unit tests, we write use ExUnit.Case
, and we get the test macro and assertion support. When we write an OTP server, we write use GenServer
, and we get both a behaviour that documents the gen_server
callback and default implementations of those callbacks.
Typically, the __using__
callback will be implemented as a macro, because it’ll be used to invoke code in the original module.
Putting it together: tracing method calls
Let’s work through a larger example. We want to write a module called Tracer
. If we use Tracer
in another module, entry and exit tracing will be added to any subsequently defined function. For example, look at the following:
defmodule Test do
use Tracer
def puts_sum_three(a,b,c), do: IO.inspect(a+b+c)
def add_list(list), do: Enum.reduce(list, 0, &(&1+&2))
end
Test.puts_sum_three(1,2,3)
Test.add_list([5,6,7,8])
The code above gives this output:
==> call puts_sum_three(1, 2, 3)
6
<== returns 6
==> call add_list([5,6,7,8])
<== returns 26
Our approach to writing this kind of code is to start by exploring what we have to work with and then to generalize. The goal is to metaprogram as little as possible.
It looks as if we have to override the def
macro, which is defined in Kernel
. So, let’s do that and see what gets passed to def
when we define a method.
Get hands-on with 1400+ tech skills courses.