Modules

Modules provide namespaces for things you define. We’ve already seen them encapsulating named functions. They also act as wrappers for macros, structs, protocols, and other modules.

If we want to reference a function defined in a module from outside that module, we need to prefix the reference with the module’s name. We don’t need that prefix if code references something inside the same module as itself, as in the following example:

 defmodule​ Mod ​do
 def​ func1 ​do
  IO.puts ​"​​in func1"
 end
 def​ func2 ​do
  func1
  IO.puts ​"​​in func2"
 end
 end
 
 Mod.func1
 Mod.func2

func2 can call func1 directly because it is inside the same module. Outside the module, you have to use the fully qualified name, Mod.func1.

Just as you do in your favorite language, Elixir programmers use nested modules to impose structure for readability and reuse. After all, every programmer is a library writer.

To access a function in a nested module from the outside scope, prefix it with all the module names. To access it within the containing module, use either the fully qualified name or just the inner module name as a prefix.

 defmodule​ Outer ​do
 defmodule​ Inner ​do
 def​ inner_func ​do
 end
 end
 
 def​ outer_func ​do
  Inner.inner_func
 end
 end
 
 Outer.outer_func
 Outer.Inner.inner_func

Module nesting in Elixir is an illusion—all modules are defined at the top level. When we define a module inside another, Elixir simply prepends the outer module name to the inner module name, putting a dot between the two. This means we can directly define a nested module.

 defmodule​ Mix.Tasks.Doctest ​do
 def​ run ​do
 end
 end
 
 Mix.Tasks.Doctest.run

It also means there’s no particular relationship between the modules Mix and Mix.Tasks.Doctest.

Directives for Modules

Elixir has three directives that simplify working with modules. All three are executed as your program runs, and the effect of all three is lexically scoped—it starts at the point the directive is encountered, and stops at the end of the enclosing scope. This means a directive in a module definition takes effect from the place you wrote it until the end of the module; a directive in a function definition runs to the end of the function.

The import Directive

The import directive brings a module’s functions and/or macros into the current scope. If you use a particular module a lot in your code, import can cut down the clutter in your source files by eliminating the need to repeat the module name time and again.

For example, if you import the flatten function from the List module, you’d be able to call it in your code without having to specify the module name.

mm/import.exs
 defmodule​ Example ​do
 def​ func1 ​do
  List.flatten [1,[2,3],4]
 end
 def​ func2 ​do
 import​ List, ​only:​ [​flatten:​ 1]
  flatten [5,[6,7],8]
 end
 end

The full syntax of import is

 import Module [, only:|except: ]

The optional second parameter lets you control which functions or macros are imported. You write only: or except:, followed by a list of name: arity pairs. It is a good idea to use import in the smallest possible enclosing scope and to use only: to import just the functions you need.

 import​ List, ​only:​ [ ​flatten:​ 1, ​duplicate:​ 2 ]

Alternatively, you can give only: one of the atoms :functions or :macros, and import will bring in only functions or macros.

The alias Directive

The alias directive creates an alias for a module. One obvious use is to cut down on typing.

 defmodule​ Example ​do
 def​ compile_and_go(source) ​do
  alias My.Other.Module.Parser, ​as:​ Parser
  alias My.Other.Module.Runner, ​as:​ Runner
  source
  |> Parser.parse()
  |> Runner.execute()
 end
 end

We could have abbreviated these alias directives to

 alias My.Other.Module.Parser
 alias My.Other.Module.Runner

because the as: parameters default to the last part of the module name. We could even take this further, and do this:

 alias My.Other.Module.{Parser, Runner}

The require Directive

You require a module if you want to use any macros it defines. This ensures that the macro definitions are available when your code is compiled. We’ll talk about require when we discuss macros.