DEV Community

Cover image for Exports dependencies in Elixir 1.11
Takanori Ishikawa
Takanori Ishikawa

Posted on • Originally published at metareal.blog

Exports dependencies in Elixir 1.11

Photo by LĂZĂRESCU ALEXANDRA on Unsplash

Last year, in October 2020, Elixir 1.11 was released 🎉 I looked at one of the compiler improvements, Exports dependencies, including its behavior.

How the "Exports dependencies" improves compilation?

Prior to Elixir 1.11, if module A depends on module B by import or require, module B was considered as Compile time dependencies of module A.

Since Elixir 1.11, if module A uses only the public interface of module B (struct or public functions) is now Exports dependencies, and there is no need to recompile module A unless the public interface of module B changes. 1

This will reduce the number of files that need to be recompiled when the source code is changed, shortening the "fix - build - run" cycle. This is great because the compilation time required for a build is an important indicator that directly affects development productivity.

Try Exports dependencies in example

You can get an idea about Exports dependencies from coding an example. Consider the two modules below. 2

moduleA.ex

defmodule ElixirV11.ModuleA do
  alias ElixirV11.ModuleB

  import ModuleB, only: [fetch_name: 1]

  def hello(%ModuleB{} = m) do
    case fetch_name(m) do
      {:ok, name} ->
        IO.puts("Hello, #{name}!")

      :error ->
        IO.puts("Who?")
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

moduleB.ex

defmodule ElixirV11.ModuleB do
  defstruct name: nil

  def new(name), do: %__MODULE__{name: name}

  def fetch_name(%__MODULE__{name: nil}), do: :error
  def fetch_name(%__MODULE__{name: name}), do: {:ok, name}
end
Enter fullscreen mode Exit fullscreen mode

ModuleA depends on the struct and fetch_name/1 functions of ModuleB. This is why import ModuleB, only: [fetch_name: 1] in moduleA.ex was Compile time dependencies before Elixir 1.10. However, since these two are public interfaces, starting with Elixir 1.11, they will be Exports dependencies.

So, when you change moduleB.ex, you need to recompile moduleA.ex in Elixir 1.10, but not in Elixir 1.11.

# Elixir 1.10
$ touch lib/moduleB.ex    
$ mix compile --verbose
Compiling 1 file (.ex)
Compiled lib/moduleB.ex
Compiled lib/moduleA.ex

# Elixir 1.11
$ touch lib/moduleB.ex 
$ mix compile --verbose
Compiling 1 file (.ex)
Compiled lib/moduleB.ex
Enter fullscreen mode Exit fullscreen mode

Let's change the implementation of ModuleB without changing the public interface.

moduleB.ex

defmodule ElixirV11.ModuleB do
  defstruct name: 1

  def new(name), do: %__MODULE__{name: check_name!(name)}

  def fetch_name(%__MODULE__{name: nil}), do: :error
  def fetch_name(%__MODULE__{name: name}), do: {:ok, name}

  defp check_name!(%__MODULE__{name: name}) when is_binary(name), do: name
end
Enter fullscreen mode Exit fullscreen mode

Added a private function called check_name!/1 and changed it to be used in new/1. Since this does not change the public interface, it should not need to be recompiled.

# Elixir 1.10
$ mix compile --verbose
Compiling 1 file (.ex)
Compiled lib/moduleB.ex
Compiled lib/moduleA.ex

# Elixir 1.11
$ mix compile --verbose
Compiling 1 file (.ex)
Compiled lib/moduleB.ex
Enter fullscreen mode Exit fullscreen mode

As expected, ModuleA do not need to be compiled in Elixir 1.11.

Exports dependencies in a real project

The commit says as follows:

making imports more feasible for large projects.

As this commit says, it will be easy to use import/2 in large projects.

In fact, in the project I'm working on now, I changed the modules that manipulate Repo, which is imported at various points, to find out the number of files that are recompiled.

  • Elixir 1.10 - 48
  • Elixir 1.11 - 14

The number of recompiled files had decreased dramatically 🎉


  1. However, if you are using macros, it will be Compile time dependencies. This is explained in detail in Dependencies types on the mix xref page. 

  2. The change to the compiler is probably this. Do not make requires/imports compile-time dependencies · elixir-lang/elixir@9a6db66 

Top comments (0)