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.
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 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.
| 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 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} |
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.