use
just calls the__using__
macro on the specified module.
You might have run into that explanation of use
but that does not explain much even though that's exactly what it does.
You might have used the use
statement in Elixir if you run into using OTP, Phoenix, Ecto or any major library, so did I. But I was confused that what's use
, what's the magic happening behind that statement.
In this post we are going to look how use
is being used by various Elixir libraries and how it removes the boilerplate that would have been required otherwise.
Lets use use
Elixir has great metaprogramming capabilities and it has macros to generate code at compile time. Wouldn't it be also great if library developers can inject code into your module so that they can decrease the boilerplate for you.
This is where use
comes in. When you call use
in your module then that modules __using__
macro is called in which the developer can generate whatever code they want. It also takes an arguments list. Let's see an example of it
defmodule AwesomeLibrary do
defmacro __using__(_) do
quote do
def print(s), do: IO.puts(s)
end
end
end
Here we have defined a module in which under __using__
macro we inject a function print/1
which when called prints whatever is passed to it.
defmodule TestLibrary do
use AwesomeLibrary
end
iex(1)> TestLibrary.print("Hello World")
Hello World
:ok
As you can see that the module TestLibrary
has no function print/1
but when we call use AwesomeLibrary
it calls the __using__
macro which injects the print/1
function into the TestLibrary
module. So in iex calling TestLibrary.print("Hello World")
works.
Now let's see some examples of how use
is being used in various libraries.
use
GenServer
GenServer is a behaviour module which allows you to write process which maintain state and run code synchronously & asynchronously. In Elixir you call use GenServer
and implement the relevant behaviour functions you want e.g. init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3
. If you have used GenServer you know that you don't have to define every method of the GenServer, only the one you need. Actually if it were Erlang you would have to implement all the functions whether you want it or not, yet you don't have to do that in Elixir, ever wondered why?
Thanks to use
you don't have to. Let's see the __using__
of the GenServer
defmacro __using__(_) do
quote location: :keep do
@behaviour :gen_server
@doc false
def init(args) do
{:ok, args}
end
@doc false
def handle_call(msg, _from, state) do
# We do this to trick dialyzer to not complain about non-local returns.
case :random.uniform(1) do
1 -> exit({:bad_call, msg})
2 -> {:noreply, state}
end
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
end
@doc false
def handle_cast(msg, state) do
# We do this to trick dialyzer to not complain about non-local returns.
case :random.uniform(1) do
1 -> exit({:bad_cast, msg})
2 -> {:noreply, state}
end
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{:ok, state}
end
defoverridable [init: 1, handle_call: 3, handle_info: 2,
handle_cast: 2, terminate: 2, code_change: 3]
end
end
As you can see that the __using__
macro implements the Erlang behaviour :gen_server
and it generates the default implementation of all the required functions and also declares them overridable. This way it reduces the amount of code you need to generate as there will already be a default implementation for the function and GenServer will just work.
You can use the same technique when you create new behaviours.
use
Ecto
In Ecto if you want to define the schema you call use Ecto.Model
in that module. Here the use Ecto.Model
acts as an umbrella module that adds all other relevant libraries so you don't have to add bunch of import statements yourself. Let's see the __using__
of Ecto.Model
module.
defmacro __using__(_opts) do
quote do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query, only: [from: 2]
import Ecto.Model
use Ecto.Model.OptimisticLock
use Ecto.Model.Timestamps
use Ecto.Model.Dependent
use Ecto.Model.Autogenerate
use Ecto.Model.Callbacks
end
end
Here as you can see it's importing bunch of libraries so that you can use the functions in it without prepending with module name and it also further calls use
of other module.
use
Protobuf
Protobuf is a language independent protocol developed by Google to serialize structured data to send over the wire and then deserialize it, similar to XML but faster and compact.
In Elixir exprotobuf is library that helps in generating code which makes it pretty straight forward for you to use protobuf.
Following is an example of exprotobuf
defmodule Messages do
use Protobuf, """
message Msg {
required uint32 value = 1;
}
"""
end
In this example the use
takes a string parameter in which we define a Msg
type in protobuf DSL which has one field i.e. value
. When the code runs you have function Messages.Msg.new, Messages.Msg.encode, Messages.Msg.decode
which helps you in creating, encoding and decoding your Msg
type respectively.
In __using__
macro protobuf parses over the protobuf DSL and generates respective modules and functions.
Go use use
Now you should have a better idea of how use
is being used by various libraries and can leverage it in your modules as well to decrease the boilerplate.