humadroid.io is an employee and performance management software. It's an unique tool allowing everyone to be in the loop - by having up to date info about co-workers, time-off, benefits, assets, helping with one-on-ones, being a go-to place for company-wide announcements.
Check out humadroid.io
Elixir Course Online | Prograils - Software Development Company
- Why Elixir for Software Development?
- Installing Elixir
- Running Elixir
- Useful links
- Data types
- Data collections
- Immutability of data
- Elixir Pattern Matching: Course Online | Prograils
- Control flow
- Anonymous functions
- Modules and named functions
Modules and named functions
Before we start with modules and named functions we should get back to file compilation for a second. Let's assume you have math.exs file which declares a Math module. You want to use this module inside of IEx to test it out. How to do that? There are two ways:
Pass the file name as iex argument:
➜ iex math.exs
Compile and load the file inside of IEx with c helper (c stands for compile):
iex> c "math.exs"
From now on, we will declare multi-line modules and functions which are hard to fix in case of a typo in IEx. It's better to use one of the above techniques of file compilation and load the code from that file.
There are numerous libraries written in Elixir, and each of them may declare functions with the same name. Sooner or later you may find yourself in a situation of naming conflict. You can deal with this issue by structuring your code in modules. In fact, Elixir named functions must be written inside modules.
We've already used modules in previous chapters. Let's recall one use case:
Enum.map array, fn num -> num * 2 end
We used map/2 named function declared inside of Enum module which holds logic capable of operating on an enumerable data type. Whenever you declare functions operating on a similar data type or in a similar way, you should create a module and put logic into it. In other words, a module is just a container.
Let's declare our first module:
defmodule Calculator do def sum(a, b) do a + b end end
We define a module with defmodule macro. In this case, module is named Calculator. Internally, module names are just atoms. When you write a name starting with an uppercase letter, such as Calculator in our case, Elixir converts it internally into an atom called Elixir.Calculator.
iex> is_atom Calculator true iex> to_string Calculator "Elixir.Calculator" iex> Calculator == :"Elixir.Calculator" true
Knowing that, we can call sum function in two ways (first is preferred):
iex> Calculator.sum(1, 2) 3 iex> :"Elixir.Calculator".sum(1, 2) 3
When calling a function of given module, you should follow this structure:
As you can see, invocation of the named function is always preceded by module name (within which it's declared), and . dot.
Modules can be nested:
defmodule OuterModule do defmodule InnerModule do def func do end end end # in IEx iex> OuterModule.InnerModule.func nil
You can declare a nested module by putting it inside of another module declaration and then access it with the . operator. Module nesting is, in fact, another syntactic sugar. You can rewrite it this way:
defmodule OuterModule.InnerModule do def func do end end
When we define a module inside another, Elixir prepends the outer module name to the internal module name, putting a dot between the two.
Sometimes module names can be pretty long, especially when we're dealing with nested structure. To solve that issue, you can create an alias for a module:
defmodule One do defmodule Two do defmodule Three do def hello do IO.puts "hello" end end end end # in IEx iex> alias One.Two.Three, as: Three One.Two.Three iex> Three.hello hello :ok
You can declare alias with
alias macro, which takes optional parameter as: NewModuleName where NewModuleName is the alias itself. If you don't specify as parameter, by default alias will inherit name from the last module, in this case, Three:
iex> alias One.Two.Three One.Two.Three iex> Three.hello hello :ok
A module can have attributes which act as metadata:
defmodule Math do @pi 3.14 end
You can assign a value to attribute with the following syntax:
In the Math module we defined @pi attribute which holds 3.14 value. This attribute is specific to a module within which it's declared (cannot be accessed elsewhere). You can access attributes in named functions declared within the same module. However, when you want to change an attribute value, you can only do that in the scope of a module - not inside of a function.
This will work:
defmodule Math do @pi 3.14 # @pi is from now on equal to 3.14 @pi 3.14159265 # @pi is re-declared with value 3.14159265 end
This won't work:
defmodule Math do @pi 3.14 def func do @pi 3.14159265 end end ** (ArgumentError) cannot set attribute @pi inside function/macro
Once you have module declared, you can extend it:
defmodule Math do @pi 3.14 def pi do @pi end end defmodule Math do # Extending @euler 2.72 def euler do @euler end end # in IEx iex> Math.pi 3.14 iex> Math.euler 2.72
Let's get back to our example of sum function:
defmodule Calculator do def sum(a, b) do a + b end end
We already know the structure of the module declaration. What we have not yet covered is a sum function. Let's break it down:
declaration starts with def keyword;
name of a function should be written in snake case;
you can declare multiple (or none) arguments of a function in brackets;
after brackets, you have to provide do ... end block;
in the block, you declare the body of a function.
You should notice that named functions are just like anonymous functions, but with the name assigned.
You can use the same guard clauses in named functions as in anonymous functions:
defmodule Calculator do def divide(a, b) when b != 0 do a / b end end
Let's execute it:
iex> Calculator.divide(5,1) 5.0 iex> Calculator.divide(5, 0) ** (FunctionClauseError) no function clause matching in Calculator.divide/2 The following arguments were given to Calculator.divide/2: # 1 5 # 2 0 iex:5: Calculator.divide/2
In Elixir, a named function is identified by module, name and number of parameters (arity). Let's see what happens when we execute sum function with the wrong number of arguments:
iex> Calculator.sum(5) ** (UndefinedFunctionError) function Calculator.sum/1 is undefined or private. Did you mean one of: * sum/2 Calculator.sum(5)
As you can see, an error is raised. We can observe that Elixir is looking for:
which is sum function declared withing
Calculator module with arity 1. It can't find it since there is only function with arity 2:
Did you mean one of: * sum/2
Multiple functions with the same name
Since arity is part of a named function definition, we can create multiple functions with the same name but a different number of arguments:
defmodule Calculator do def sum(_) do IO.puts "You need to specify at least two arguments" end def sum(a, b) do a + b end def sum(a, b, c) do a + b + c end def sum(a, b, c, d) do a + b + c + d end end # in IEx iex> Calculator.sum(1) You need to specify at least two arguments :ok iex> Calculator.sum(1, 2) 3 iex> Calculator.sum(1, 2, 3) 6 iex> Calculator.sum(1, 2, 3, 4) 10
There are a couple of things to note here:
each of these functions is different for Elixir because of their rarity,
function evaluation starts from the top,
function with the same arity will be executed.
But, there is more than that. In the previous chapter, we covered how anonymous functions use pattern matching to bind their parameter list to the passed arguments. It works the same way with named functions. We are going to use list iterator to illustrate how it works:
defmodule Collection do def each(, _func) do end def each([h | t], func) when is_function(func) do func.(h) each(t, func) end end
Let's break it down:
both each function declarations take two arguments: an array and anonymous function,
first declaration matches when an empty array is passed as a first argument,
_func means that we don't care what is the second argument as long as the first one is an empty array,
the second declaration assumes that non-empty array and a function are passed,
[h | t] uses pattern matching to extract head and tail from an array,
we use is_function(func) guard clause to make sure that the second argument is a function,
func.(h) executes a function with a head of given array,
each(t, func) calls the same function and sets tail as an array.
Let's see how it works in action:
iex> Collection.each [1,2,3], &IO.puts/1 1 2 3 nil
In this case, each function was executed four times with following arguments:
[1,2,3], &IO.puts/1 -> it prints 1.
[2,3], &IO.puts/1 -> it prints 2.
, &IO.puts/1 -> it prints 3.
, &IO.puts/1 -> it doesn't print anything, since each(, _func) is called.
In Elixir, recursion and pattern matching against different function arguments are used a lot. You will see it quite often. Remember when we noted in the control flow chapter that "in Elixir, we should use these statements as rarely as possible"? It's because you can declare multiple function definitions and control the flow with arguments pattern matching. It brings more clarity and less complexity.
You should already notice that both module and function requires
do...end block. In Elixir, it's one way of grouping expressions and passing them to other code. This syntax is only syntactic sugar. There are two ways of declaring function block:
multi-line module and function:
defmodule Calculator do def power(number) do number * number end end
one-line module and function:
defmodule Calculator do def power(number), do: number * number end # or defmodule Calculator, do: (def power(number), do: number * number)
The "new" thing is a one-line version which is in fact the primary way of passing a block. During compilation, multi-line version of the block is translated into one-line do: ... version. When it comes to functions, one-line version might be convenient. In case of modules, it's much better to use multi-line version.
In many programming languages, it's possible to assign a default value to function parameters. Elixir is no different. Let's examine the following function:
defmodule Screen do def draw_line(opts \\ ) do IO.inspect opts end end
Screen module has
draw_line/1 function which has one thing that may look different to you:
opts \\ 
\ operator defines a default value for opts variable. Any expression is allowed to serve as a default value, but it won’t be evaluated during the function definition. In this case, we want opts to store an empty array when no value is passed.
iex> Screen.draw_line  iex> Screen.draw_line(width: 50) [width: 50]
Function variables are bound to values given to it via pattern matching. Pattern matching fails when the number of values on both sides is not equal. That's why you can't invoke a method with the wrong number of arguments. However, as you already know, we can declare default values for arguments. Let's take a look at this function:
defmodule Example do def func(first, second \\ 0, third \\ 0, fourth) do [first, second, third, fourth] end end
There are two arguments with default value: second and third. Let's see what happens when we call this function with different number of arguments:
iex> Example.func(1, 1, 1, 1) [1, 1, 1, 1] iex> Example.func(1, 1, 1) [1, 1, 0, 1] iex> Example.func(1, 1) [1, 0, 0, 1] iex> Example.func(1) ** (UndefinedFunctionError) function Example.func/1 is undefined or private. Did you mean one of: * func/2 * func/3 * func/4 Example.func(1)
When you pass four arguments all of them match to value 1. However, when you pass fewer arguments, three, for instance, the last argument with a default value will be bound to
0. Arguments with a default value are evaluated from end to beginning of a list. The actual rule for passing a minimum number of values to function is the following:
(number of arguments) - (number of arguments with default values)
Functions can also be private - one that can be called only within the module that declares it.
defmodule Example do def func, do: pfunc defp pfunc, do: IO.puts "Hello from private function" end # in IEx iex> Example.func Hello from private functions :ok iex> Example.pfunc ** (UndefinedFunctionError) function Example.pfunc/0 is undefined or private. Did you mean one of: * func/0 Example.pfunc()
As you can see, we can call the private function inside of a public function, but we can't do it publicly via the module. There is also one limitation when it comes to private functions - they can't have the same name and arity as functions inside the same module:
defmodule Example do def func defp func end # in IEx ** (CompileError) iex:3: defp func/0 already defined as def
We are already familiar with name/arity function notation. You can use this notation to capture a function and bind it to variable. Let's examine this example:
iex> func = &String.length/1 &String.length/1 iex> func.("Hello world") 11
Capture operator (&) allows named functions to be assigned to variables and passed as arguments in the same way we assign, invoke and pass anonymous functions. Since we treat such a function as anonymous, you have to invoke it via .().
In object oriented programming languages it's very easy to chain functions call on an object:
Imagine you want to perform similar functions call on a data in Elixir:
Well, it doesn't look like readable code, does it? You don't have to worry about that since there is a solution for this issue - pipe operator. The same functions call can be rewritten this way:
data |> func1 |> func2 |> func3 |> func4
Elixir is all about transforming data. Pipe operator illustrates it perfectly. Let's break it down:
we start with data,
>` means that returned value of an expression on the left side of this operator will be passed as the first argument of a function on the right side,
func1 will be called with data attribute: func1(data),
the result of func1(data) will be passed as the first argument of func2,
execution of functions will continue until it reaches func4.
In short, these two expressions are equal:
fun4(fun3(fun2(fun1(data)))) == data |> func1 |> func2 |> func3 |> func4
You can also chain functions call in multi-line:
transformed_data = data |> func1 |> func2 |> func3 |> func4
When one-line version affects the readability of code, you should always use multi-line version.
To help you understand pipe operator let's write a function that:
takes number higher than 0 as an argument,
creates an array containing a range of elements: 0..argument,
converts each element to a string,
joins all string elements to one string.
defmodule DataTransformator do def range_to_string(num) when is_integer(num) and num > 0 do 0..num |> Enum.to_list |> Enum.map(&Integer.to_string/1) |> Enum.join end end # in IEx iex> DataTransformator.range_to_string(10) "012345678910"
Range 0..num is converted into list with Enum.tolist/1. List of integer values is then transformed with Enum.map(&Integer.tostring/1) into a list of strings. Notice that we pass Integer.to_string/1 function as anonymous. In the end, elements of the array are merged into one string. There is also an important thing - you need parentheses when piping into a function call.
It means that this would not work:
|> Enum.map &Integer.to_string/1
but this will: