Joseph Kain bio photo

Joseph Kain

Professional Software Engineer learning Elixir.

Twitter LinkedIn Github

There a several special forms for referring to other modules in Elixir. These include:

  • use
  • import
  • require
  • alias

These each have their own meaning but until you get used to them it can be hard to know which one to use.

To demonstrate each of these special forms let’s create a new test project.

$ mix new use_import_require
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/use_import_require.ex
* creating test
* creating test/test_helper.exs
* creating test/use_import_require_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd use_import_require
    mix test

Run "mix help" for more commands.

This comes with a brand new empty module:

defmodule UseImportRequire do
end

Though the course of this post we’ll fill in and make use of use, import, require, and alias.

Elixir alias

I’ll start with alias whose only purpose is to make it easier to type (and maybe read) code. To make an alias we’ll need another module. Let’s add one:

# lib/use_import_require/alias_me.ex
defmodule UseImportRequire.AliasMe do
  def function do
    IO.puts "#{__MODULE__}.function"
  end
end

Now, if we want to call this function from UseImportRequire we would have to reference with its fully qualified name: UseImportRequire.AliasMe.function. But that’s a lot to write so we can use alias like this and use a shorter form of the name:

defmodule UseImportRequire do
  alias UseImportRequire.AliasMe

  def alias_test do
    AliasMe.function
  end
end

And that’s it. That’s what alias does, it let’s you use shorter names. Of course, there is some more to the syntax like aliasing more than one module at a time. And you have some flexibility about how many nested modules you alias away. But in term of purpose, this is it: alias shortens the name.

There are of course some tradeoffs to consider with alias. In the simple example above there’s not much to consider. Most likely everything in this app will start with the UseImportRequire so omitting it has little cost and makes code more readable by eliminating noise. However, as the number of modules in your project grows you may end up with multiple modules with the same leaf module name. Using alias you might end up with code that’s confusing to the reader. You should ask yourself, if I alias am I going to create any ambiguity?

Edit Jan 23, 2016: As Philip Claren pointed out in the comments, it is also possible to pick the name for your alias using the alias-as form. Adding on to our example:

defmodule UseImportRequire do
  alias UseImportRequire.AliasMe
  alias UseImportRequire.AliasMe, as: AnotherName

  def alias_test do
    AliasMe.function
  end

  def alias_as_test do
     AnotherName.function
  end
end

We see we can add an as: name parameter to alias to set any aliased name we desire. I’ve picked AnotherName in the example.

Alias-as can be really helpful if you have two modules with the same leaf-name. For example, if you had UseImportRequire.FirstKind.AliasMe and UseImportRequire.SecondKind.AliasMe then simply aliasing both modules wouldn’t work. There would be ambiguity. With alias-as you can choose a different name for one of the modules.

Elixir import

Now, suppose you still think that AliasMe.function is too much to write. You could use import. For an import example I’ll create a module to import:

# lib/use_import_require/import_me.ex
defmodule UseImportRequire.ImportMe do
  def function do
    IO.puts "#{__MODULE__}.function"
  end
end

and, I’ll reference this from UseImportRequire:

defmodule UseImportRequire do
  alias UseImportRequire.AliasMe
  import UseImportRequire.ImportMe

  def alias_test do
    AliasMe.function
  end

  def import_test do
    function
  end
end

So, now we can reference the function as just function. Nice an short. But at what cost does this come?

It is much easier to create ambiguity in this case. For example, if we imported both the AliasMe and ImportMe modules we’d end up with two functions named function and in fact would not compile. Note that the compilation error is lazy in that it would fail unless an ambiguously named function is actually called.

I would recommend using import sparingly. It removes a lot of information which can be a burden for any reader of your code. However, there are a few cases where import is helpful. If you are writing a module that is very focused in that it makes heavy use of a specific module then import may make sense. One common example is that in a module that makes extensive use of Ecto queries it is common to import Ecto.Query.

The import macro also allows importing of specific functions or macros. This limits “namespace pollution” and can reduce the chance of ambiguity or confusion. Again, this is common with Ecto.Query - the documentation recommends:

import Ecto.Query, only: [from: 2]

in order to import only the Ecto.Query.from/2 macro.

Elixir require

The require macro instructs the compiler to load the specified module before compiling the containing module. This is only necessary if you want to reference macros from the specified module. For example, we would need:

defmodule UseImportRequire do
  require UseImportRequire.RequireMe
end

if the RequireMe module contained a macro we wanted to use. Nothing special is done to the macro name. We would still need to reference it with its fully qualified name.

In this way require and import have some overlapping purpose. Either can be used to access macros in other modules. Though their effects on the namespace differ.

Elixir use

The use macro invokes a special macro, called __using__/1, from the specified module. Here’s an example:

# lib/use_import_require/use_me.ex
defmodule UseImportRequire.UseMe do
  defmacro __using__(_) do
    quote do
      def use_test do
        IO.puts "use_test"
      end
    end
  end
end

and we add this line to UseImportRequire:

use UseImportRequire.UseMe

Using UseImportRequire.UseMe defines a use_test/0 function through invocation of the __using__/1 macro.

This is all that use does. However, it is common for the __using__ macro to in turn call alias, require, or import. This in turn will create aliases or imports in the using module. This allows the module being used to define a policy for how its functions and macros should be referenced. This can be quite flexible in that __using__/1 may set up references to other modules, especially submodules.

The Phoenix framework makes use of use and __using__/1 to cut down on the need for repetitive alias and import calls in user defined modules.

Here’s an nice and short example from the Ecto.Migration module:

defmacro __using__(_) do
  quote location: :keep do
    import Ecto.Migration
    @disable_ddl_transaction false
    @before_compile Ecto.Migration
  end
end

The Ecto.Migration.__using__/1 macro includes an import call so that if use Ecto.Migration you also import Ecto.migration. It also sets up a module property which I assume control Ecto’s behavior.

To recap: the use macro just invokes the __using__/1 macro of the specified module. To really understand what that does you need to read the __using__/1 macro.

Referencing Modules

Now, which of the above macros should you use if you just want to call functions from another module? The answer is: none. Instead, you can just reference the functions directly. Here’s an example module we can reference:

# lib/use_import_require/reference_me.ex
defmodule UseImportRequire.ReferenceMe do
  def function do
    IO.puts "#{__MODULE__}.function"
  end
end

and here is how we can access the function:

def reference_test do
  UseImportRequire.ReferenceMe.function
end

That’s it. We don’t need to use, import, or require the module. We just use the fully qualified name.

Scope

As I’ve mentioned there are tradeoffs for using alias and import between convenience and clarity. There is another way to help mitigate this tradeoff. The alias and import macros don’t need to be called at the outer module scope as we have been using them. They can, for example, be called from within another function. Here’s an example using import:

defmodule UseImportRequire.WithScope do
  def scope_test do
    import UseImportRequire.ReferenceMe
    function
  end
end

I’ve had to add this to a new module rather than UseImportRequire due to a problem with ambiguity.

Using this scoped version we can’t access function without a qualified name in another function. for example, this won’t compile:

defmodule UseImportRequire.WithScope do
  def scope_test do
    import UseImportRequire.ReferenceMe
    function
  end

  def failing_scope_test do
    function
  end
end

The alias macro can be used within a function in the same way.

Calling import and alias within a narrow scope this way helps limit the amount of “namespace pollution” that happens in your code.

Also, using a narrow scope this way makes the code a little clearer by keeping the alias or import closer to the reference thereby making it easier for the reader to keep track of what’s going on. This can be especially useful as your modules start to get longer.

An Example

Here’s a really nice example of using import:

defmodule Orthrus.Repo.Migrations.CreateUser do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :name, :string
      add :username, :string
      add :password_hash, :string
      add :email, :string

      timestamps
    end

  end
end

The use Ecto.Migration call invokes Ecto.Migration.__using__/1. And we saw above that this macro in turn calls import Ecto.Migration. The import allows us to write very clean code in the migration. We can call create, add, timestamps without needing to clutter up the code with an Ecto.Migration prefix.

For migrations, this is a good tradeoff a migration is narrowly focused task. When you read these references to create table, and add you are in the mindset of thinking about database migrations so this code makes sense.

If you have other tasks that are not as focused you may want to ask yourself if import is the right choice.