8 Modules¶
{% include toc.html %}
In Elixir we group several functions into modules. We’ve already used many different modules in the previous chapters like the ``String` module </docs/stable/elixir/String.html>`__:
iex> String.length "hello"
5
In order to create our own modules in Elixir, we use the defmodule
macro. We use the def
macro to define functions in that module:
iex> defmodule Math do
...> def sum(a, b) do
...> a + b
...> end
...> end
iex> Math.sum(1, 2)
3
In the following sections, our examples are going to get a bit more complex, and it can be tricky to type them all in the shell. It’s about time for us to learn how to compile Elixir code and also how to run Elixir scripts.
8.1 Compilation¶
Most of the time it is convenient to write modules into files so they
can be compiled and reused. Let’s assume we have a file named
math.ex
with the following contents:
defmodule Math do
def sum(a, b) do
a + b
end
end
This file can be compiled using elixirc
:
$ elixirc math.ex
This will generate a file named Elixir.Math.beam
containing the
bytecode for the defined module. If we start iex
again, our module
definition will be available (provided that iex
is started in the
same directory the bytecode file is in):
iex> Math.sum(1, 2)
3
Elixir projects are usually organized into three directories:
- ebin - contains the compiled bytecode
- lib - contains elixir code (usually
.ex
files) - test - contains tests (usually
.exs
files)
When working on actual projects, the build tool called mix
will be
responsible for compiling and setting up the proper paths for you. For
learning purposes, Elixir also supports a scripted mode which is more
flexible and does not generate any compiled artifacts.
8.2 Scripted mode¶
In addition to the Elixir file extension .ex
, Elixir also supports
.exs
files for scripting. Elixir treats both files exactly the same
way, the only difference is in intention. .ex
files are meant to be
compiled while .exs
files are used for scripting, without the need
for compilation. For instance, we can create a file called math.exs
:
defmodule Math do
def sum(a, b) do
a + b
end
end
IO.puts Math.sum(1, 2)
And execute it as:
$ elixir math.exs
The file will be compiled in memory and executed, printing “3” as the result. No bytecode file will be created. In the following examples, we recommend you write your code into script files and execute them as shown above.
8.3 Named functions¶
Inside a module, we can define functions with def/2
and private
functions with defp/2
. A function defined with def/2
can be
invoked from other modules while a private function can only be invoked
locally.
defmodule Math do
def sum(a, b) do
do_sum(a, b)
end
defp do_sum(a, b) do
a + b
end
end
Math.sum(1, 2) #=> 3
Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)
Function declarations also support guards and multiple clauses. If a function has several clauses, Elixir will try each clause until it finds one that matches. Here is an implementation of a function that checks if the given number is zero or not:
defmodule Math do
def zero?(0) do
true
end
def zero?(x) when is_number(x) do
false
end
end
Math.zero?(0) #=> true
Math.zero?(1) #=> false
Math.zero?([1,2,3])
#=> ** (FunctionClauseError)
Giving an argument that does not match any of the clauses raises an error.
8.4 Function capturing¶
Throughout this tutorial, we have been using the notation name/arity
to refer to functions. It happens that this notation can actually be
used to retrieve a named function as a function type. Let’s start
iex
and run the math.exs
file defined above:
$ iex math.exs
iex> Math.zero?(0)
true
iex> fun = &Math.zero?/1
&Math.zero?/1
iex> is_function fun
true
iex> fun.(0)
true
Local or imported functions, like is_function/1
, can be captured
without the module:
iex> &is_function/1
&:erlang.is_function/1
iex> (&is_function/1).(fun)
true
Note the capture syntax can also be used as a shortcut for creating functions:
iex> fun = &(&1 + 1)
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> fun.(1)
2
The &1
represents the first argument passed into the function.
&(&1+1)
above is exactly the same as fn x -> x + 1 end
. The
syntax above is useful for short function definitions.
In the same fashion if you want to call a function from a module, you
can do &Module.function()
:
iex> fun = &List.flatten(&1, &2)
&List.flatten/2
iex> fun.([1, [[2], 3]], [4, 5])
[1, 2, 3, 4, 5]
&List.flatten(&1, &2)
is the same as writing
fn(list, tail) -> List.flatten(list, tail) end
. You can read more
about the capture operator &
in the ``Kernel.SpecialForms`
documentation </docs/stable/elixir/Kernel.SpecialForms.html#&/1>`__.
8.5 Default arguments¶
Named functions in Elixir also support default arguments:
defmodule Concat do
def join(a, b, sep \\ " ") do
a <> sep <> b
end
end
IO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
Any expression is allowed to serve as a default value, but it won’t be evaluated during the function definition; it will simply be stored for later use. Every time the function is invoked and any of its default values have to be used, the expression for that default value will be evaluated:
defmodule DefaultTest do
def dowork(x \\ IO.puts "hello") do
x
end
end
iex> DefaultTest.dowork
hello
:ok
iex> DefaultTest.dowork 123
123
iex> DefaultTest.dowork
hello
:ok
If a function with default values has multiple clauses, it is recommended to create a function head (without an actual body), just for declaring defaults:
defmodule Concat do
def join(a, b \\ nil, sep \\ " ")
def join(a, b, _sep) when is_nil(b) do
a
end
def join(a, b, sep) do
a <> sep <> b
end
end
IO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
IO.puts Concat.join("Hello") #=> Hello
When using default values, one must be careful to avoid overlapping function definitions. Consider the following example:
defmodule Concat do
def join(a, b) do
IO.puts "***First join"
a <> b
end
def join(a, b, sep \\ " ") do
IO.puts "***Second join"
a <> sep <> b
end
end
If we save the code above in a file named “concat.ex” and compile it, Elixir will emit the following warning:
concat.ex:7: this clause cannot match because a previous clause at line 2 always matches
The compiler is telling us that invoking the join
function with two
arguments will always choose the first definition of join
whereas
the second one will only be invoked when three arguments are passed:
$ iex concat.exs
iex> Concat.join "Hello", "world"
***First join
"Helloworld"
iex> Concat.join "Hello", "world", "_"
***Second join
"Hello_world"
This finishes our short introduction to modules. In the next chapters, we will learn how to use named functions for recursion, explore Elixir lexical directives that can be used for importing functions from other modules and discuss module attributes.