Elixir nested modules, imports, and aliases

- -

Elixir dotted namespacing is not sophisticated. As in Erlang, a module name is just an atom. A namespaced module name like Nesting.Nested is really just an alias to the atom :"Elixir.Nesting.Nested"; the namespacing is a convention rather than something built into the language.

One exception is the teaspoon of syntactic sugar that sweetens defining a module within a module.

defmodule Nesting do
  defmodule Nested do
    # The Nested module is really Nesting.Nested
    def inner_hello, do: :inner_hello
  end
  # There is an implicit
  # alias Nesting.Nested here
  def outer_to_inner_hello, do: Nested.inner_hello()
end

The sugar has given us the automatic "namespacing" of Nested and the implicit alias of Nesting.Nested to Nested within the rest of the module.

This is all documented. It is also the extent of the sugar: the code below does not compile.


defmodule Deep.Nesting do
  def outer, do: :outer

  defmodule Nested do
    # WILL NOT COMPILE: Deep.Nesting would need to be directly
    # referenced or aliased
    def inner_outer, do: Nesting.outer()

    # ALSO INVALID. We would not really expect
    # Deep.Nesting functions to be imported
    def inner_outer2, do: outer()
  end
end

Ok, I lied. There are some other surprising aspects to nested modules:

  • While the outer module's functions are not imported to the inner, any imports to the outer module are also available to the inner one.
  • While the outer module is not implicitly aliased in the inner module, any modules aliased in the outer module are also aliased in the inner.
defmodule Namespace.InNamespace do
  def a_thing, do: :thing
end

defmodule Nesting do
  alias Namespace.InNamespace
  import Enum, only: [map: 2]

  defmodule Nested do
    # Enum import is available
    def inner_map, do: map([1, 2, 3], &(&1 * 2))

    # Alias also available
    def inner_thing, do: InNamespace.a_thing()
  end
end

I can not find this behaviour documented anywhere but it may be worth knowing, especially if trying to figure out import or alias clashes.

Update - 2018-11-16

José Valim has pointed out that the behaviour is (sort of) documented:

... we say that alias, import and require are lexical ... At the moment they are defined, everything below the declaration in the same code branch will have the same aliases, imports, etc

The documentation is here.

We're passionate about understanding businesses, ideas and people. Let's Talk