usejust 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.