- Elixir
What's the difference between alias, import, require and use in Elixir? A complete guide with use cases
In most programming languages we often deal with instructions responsible for handling dependencies. Elixir is no different.
In Elixir, dependency is nothing more than compiled module which for some reason you want to use in another module. There are a couple of instructions that we use in Elixir to either make it easier or possible to interact with modules.
In this blog post I'll explain and present use case examples of four of them:
alias
,require
,import
,use
.
Alias in Elixir
In Elixir, the module names might be quite long. As a result, it's very time consuming to use them a lot across the entire app. Such code is also rather difficult to read.
Let's take a look at this example:
defmodule Curiosum.Authentication.Admin do
defstruct [:id, :email]
def changeset(admin, params \\ %{}) do
# implementation is not important here
end
end
Now, imagine you want to call changeset/2
function with Curiosum.Authentication.Admin
struct as its argument. You have to use the whole module name to do that:
Curiosum.Authentication.Admin.changeset(%Curiosum.Authentication.Admin{})
The above example doesn't look good. Fortunately, we have an alias/2
macro. Its purpose is to give module an alternative name:
alias Curiosum.Authentication.Admin, as: AdminResource
AdminResource.changeset(%AdminResource{})
That's way better. With :as
option you specify the name of the alias, however, it's optional. Calling alias without :as
by default uses last part of the module name which in this case is Admin
:
alias Curiosum.Authentication.Admin
Admin.changeset(%Admin{})
It's very common to skip :as
option in Elixir world. After all calling alias in following way:
alias Curiosum.Authentication.Admin, as: Admin
is the same as:
alias Curiosum.Authentication.Admin
In real case scenario, you probably will work on a context with multiple data structures inside of it. Let's define another data structure in Curiosum.Authentication
context:
defmodule Curiosum.Authentication.User do
defstruct [:id, :email]
def changeset(user, params \\ %{}) do
# implementation is not important here
end
end
With two data structures defined, we want to use both of them in Curiosum.Authentication
context. Of course, we also want to alias both of these:
defmodule Curiosum.Authentication do
alias Curiosum.Authentication.Admin
alias Curiosum.Authentication.User
end
There is also a nice shortcut for this use case:
defmodule Curiosum.Authentication do
alias Curiosum.Authentication.{ Admin, User }
end
If for some reason you declare an alias and don't use it, you'll see a warning:
iex(1)> defmodule Curiosum.Authentication do
...(1)> alias Curiosum.Authentication.{ Admin, User }
...(1)> end
warning: unused alias Admin
iex:2
warning: unused alias User
iex:2
A warning can be skipped with :warn
option set to false
:
alias Curiosum.Authentication.{ Admin, User }, warn: false
However, this should raise a question if giving an alias without using its benefits is a good idea after all.
One more thing to note is that you can only use an alias in the same lexical scope. For example, alias inside of module is not valid outside of it:
defmodule Curiosum.Authentication do
alias Curiosum.Repo
alias Curiosum.Authentication.Admin
def get_admins do
Repo.all(Admin) # This is correct
end
end
Repo.all(Admin) # Wrong lexical scope, an error will be raised!
One of the features of Module mechanism in Elixir is avoiding naming issues. As presented in the above examples, modules might grow to pretty long names and that's why aliases are used in Elixir very frequently. You should get used to them.
Import in Elixir
Aliases are great for shortening module names but what if we use functions from given module extensively and want to skip using module name part?
You can import all functions and macros from a given module with import/2
macro:
import Curiosum.Authentication.Admin
This directive imports all public functions and macros from given module except the ones starting with underscore, for example, __build__
.
Although it's very handy, you probably don't need to import everything. In most cases, you only need a couple of functions.
You can import specific functions or macros with :only
option:
alias Curiosum.Authentication.Admin
import Admin, only: [changeset: 2]
changeset(Admin) # correct function call
You can also import everything except few of specific functions or macros with :except
option:
alias Curiosum.Authentication.Admin
import Admin, except: [changeset: 2]
changeset(Admin) # incorrect function call
Notice that you have to specify arity of functions or macro. This is due to how Elixir is designed. Functions with different arities are not the same.
Therefore you can't import all changeset
functions at once:
import Admin, only: :changeset # error will be raised
import Admin, only: [:changeset] # error will be raised
You can, however, import only functions
or macros
at once:
import Admin, only: :functions
import Admin, only: :macros
Just like an alias/2
the import/2
directive is also lexically scoped:
defmodule Curiosum.Authentication do
alias Curiosum.Authentication.Admin
import Admin, only: [changeset: 2]
...
end
changeset(Admin) # Wrong lexical scope, an error will be raised!
There is also :warn
option in import
, which is being used to silence warning generated when none of imported functions/macros are used:
import Admin, warn: false
Again, keep in mind that you should not use import when you don't need it and if you need only specific functions/macros, use :only
option.
How about importing functions with the same name and arity from different modules?
alias Curiosum.Authentication.{ Admin, User }
import Admin
import User
changeset(%{})
Since both changeset/2
functions accept any arguments there will be ambiguous call conflict:
(CompileError) function changeset/1 imported from both Curiosum.Authentication.User and Curiosum.Authentication.Admin, call is ambiguous
This is worth remembering, and a big argument against importing all functions/macros at once. Keep it in mind!
Require in Elixir
Modules public methods are globally available. This is not true to macros.
Let's declare custom_unless/2
macro in Conditional
module:
defmodule Conditional do
defmacro custom_unless(clause, do: expression) do
quote do
if(!unquote(clause), do: unquote(expression))
end
end
end
Now, look at what happens when we use custom_unless/2
just like with public methods:
iex> Conditional.custom_unless 2 < 1, do: IO.puts "It works!"
** (CompileError) iex: you must require Conditional before invoking the macro Conditional.custom_unless/2
As you can see in the error message, we need to require Conditional
module to use its macros. Why?
Macro function is being evaluated during compilation. If you want to use it, you need to compile it first. This is exactly what require/2
does.
In our example, we need to compile Conditional
module first, and only then we can use custom_unless/2
macro:
iex> require Conditional
Conditional
iex> Conditional.custom_unless 2 < 1, do: IO.puts "It works!"
It works!
:ok
You can also pass :as
option to require/2
which works exactly the same as in case of alias/2
:
iex> require Conditional, as: Cond
Conditional
iex> Cond.custom_unless 2 < 1, do: IO.puts "It works!"
It works!
:ok
Notice that require/2
is different than alias/2
, since alias/2
does not automatically compile given module and therefore we can't use its macros.
It's also different than import/2
. In fact, import/2
uses require/2
in the background to compile module, but it also makes it possible to skip module name when invoking a function.
The lexical scope also applies to require/2
statement.
Use in Elixir
One of the main ideas behind Elixir is that it should be extensible. The use
statement brings an extension point to modules.
Imagine that a couple of modules use the same dependencies, and share commons behavior. You can leverage use
to perform some sort of set up on each of these modules.
The use
macro allows you to inject any code in the current module.
Let's assume that we use Ecto
library in our Curiosum.Authentication.*
schemas. To provide a simple solution for common setup operations we can create Curiosum.Authentication.Schema
:
defmodule Curiosum.Authentication.Schema do
defmacro __using__(_opts) do
quote do
use Ecto.Schema
import Ecto
import Ecto.Changeset
import Ecto.Query
def changeset(struct, params \\ %{}) do
# assuming that changeset will perform same operations
end
end
end
end
In the above code, we can see __using__/1
macro. The whole magic happens inside of it. Any code you put into __using__/1
will be injected and executed inside of modules that use it:
defmodule Curiosum.Authentication.Admin do
use Curiosum.Authentication.Schema
end
Thanks to use Curiosum.Authentication.Schema
, the whole code inside of __using__
has been injected into Curiosum.Authentication.Admin
module.
In the background use
macro is being compiled into:
defmodule Curiosum.Authentication.Admin do
require Curiosum.Authentication.Schema
Curiosum.Authentication.Schema.__using__
end
The __using__/1
macro accepts an optional argument, which can be used inside of it as needed. We can observe that in Phoenix
with Controllers
and Views
:
use CuriosumWeb, :controller
or
use CuriosumWeb, :view
Are there any risks involving use
?
As you already know, we can put any code into __using__/1
. When you use external libraries, you might not be sure what exactly happens behind the scenes.
Read the documentation carefully and take a look what's being done inside of __using__
.
That's all for now, if you have any questions, let me know in comments below!