Let’s demystify the frequently used use
keyword in Elixir.
We can use the use
keyword in Elixir to package behaviour and implementation for use in other modules, similar to abstract base classes or mixins in other languages. Mixins reduce the amount of boilerplate in your modules, especially for library authors.
Mixins in Elixir are similar on the surface to Concerns
in Ruby and Traits
in Scala. Before we start, you should be somewhat familiar with behaviours and macros.
The use
keyword
use
requires the given module and calls the __using__
macro defined for that module. To illustrate, both examples below are equivalent:
Example
Now take a look at the Filter
module defined below:
First, we define a behaviour callback transform
with the @callback
attribute. Modules that use
the Filter
module will need to implement transform
.
We then define the __using__
macro using defmacro
. __using__
is called whenever the use
keyword is called with this module.
We can accept any _params
specified in the use
call, but we’re not using any for this example.
Within the body of the __using__
macro, we use quote
to return a quoted expression. The execution of this quoted expression is deferred until runtime, when the use
keyword is executed within the user module. This lets us inject code to user modules.
Read this if you need a refresher on macros.
Here we use the @behaviour
attribute to denote that modules that use Filter
must implement the callbacks defined in Filter
.
We define some default concrete implementations of shout/0
and greet/1
which will be injected to user modules. We then use defoverridable
to denote that it can be overriden.
Now let’s take a look at a MyFilter
module which use
the Filter
module:
In our module we override the greet/1
function with our own implementation.
If we didn’t explicitly add
greet/1
to ourdefoverridable
, new definitions ofgreet/1
will not be picked up and we would use the original definition instead.
However, compiling the above module gives us the following warning: warning: undefined behaviour function transform/1 (for behaviour Filter)
. This is because we’re missing a required behaviour implementation transform
. Let’s implement all our behaviour callbacks:
MyFilter
will now compile without warnings. With just a single line of code, we’ve injected MyFilter
with behaviours and default overridable implementation:
Other Examples
If you’ve played around with the OTP, you would have come across use GenServer
. The GenServer
module uses use
to give developers a set of default client-server functionality which can be overriden. Here is an excerpt of its __using__
macro:
Alternatives to use
If you don’t need metaprogramming, control over method overriding, nor pass in parameters to your use
call - look into using import
instead.
In Closing
Mixins are a powerful way to organize your modules that is well worth its place in your Elixir toolbox. I hope I have helped you dispel the magic of use
. You should have a better understanding on how you can use mixins to inject code into other modules and reduce boilerplate.