Elixir Course Online | Prograils - Software Development Company
Anonymous functions
Functional programming is all about transforming data. Functions are the little engines that perform that transformation. In this chapter, we are going to learn how to deal with one of the Elixir basic data type: anonymous functions.
Representation
Let's declare anonymous function:
fn (a, b) -> a * b end
We can distinguish four parts here:
declaration starts with
fn
keyword;a function can take zero or multiple arguments, in this case, there are two of them:
(a, b)
;function implementation is followed by the
->
arrow, in this case, we are multiplying arguments;declaration ends with
end
keyword.
You can write the same function in multiple lines:
fn (a, b) ->
a * b
end
You don't have to surround arguments with parentheses. It's optional:
fn a, b -> a * b end
We call this function anonymous since there is no name assigned to it. Such a function is convenient when we want to pass function to another function as one of the arguments. How to execute it? You should bind it to a variable first:
iex> multiply = fn (a, b) -> a * b end
#Function<12.128620087/2 in :erl_eval.expr/5>
iex> multiply.(5, 6)
30
Once an anonymous function is bounded to a variable, you can invoke it with .()
and pass the required number of arguments. If your function takes no arguments, you still need the parentheses to call it:
iex> hello_world = fn -> IO.puts "Hello world" end
#Function<20.128620087/0 in :erl_eval.expr/5>
iex> hello_world.()
Hello world
:ok
Arguments and pattern matching
We already know that there is no variable assignment in Elixir - there is matching. In typical programming language when you pass arguments to function, their values are assigned to variables. In Elixir it's all about pattern matching. Why is this such a big deal? Let's take a look at this function definition:
iex> open_file = fn
...(1)> {:ok, file} -> "File content: #{IO.read(file, :line)}"
...(1)> {_, error} -> "Error message: #{:file.format_error(error)}"
...(1)> end
#Function<6.128620087/1 in :erl_eval.expr/5>
iex> open_file.(File.open('elixir.txt'))
"File content: Elixir is great!\n"
iex> open_file.(File.open('not_existing_file.txt'))
"Error message: no such file or directory"
Unlike in the previous example, we have two function implementations followed by different argument tuples. In Elixir, you can pass multiple arguments options which will be pattern matched during function invocation. How does it apply to this example?
With this line of code:
open_file.(File.open('elixir.txt'))
we are trying to open elixir.txt
file. During execution, this file was present in the current directory and therefore File.open
returned tuple of two elements: :ok
status and reference to a file. It's clear that
{:ok, file}
matches the first arguments option in open_file
anonymous function. Because of that
"File content: #{IO.read(file, :line)}"
has been executed.
With this line of code:
open_file.(File.open('not_existing_file.txt'))
we are trying to open a file that is not present in current directory and because of that File.open/2
returned tuple with error status and its message. We only need the message, and that's why status binding was skipped with _
. You can pass as much possible arguments options as you want, but after all, at least one of them must match:
iex> open_file = fn
...(1)> {:ok, file} -> "File content: #{IO.read(file, :line)}"
...(1)> end
#Function<6.128620087/1 in :erl_eval.expr/5>
iex> open_file.(File.open('not_existing_file.txt'))
** (FunctionClauseError) no function clause matching in :erl_eval."-inside-an-interpreted-fun-"/1
The following arguments were given to :erl_eval."-inside-an-interpreted-fun-"/1:
# 1
{:error, :enoent}
Passing function as an argument
At the beginning of this chapter, it was mentioned that "anonymous function is convenient when we want to pass function to another function as one of the arguments". It's time to explain this statement.
Imagine you have a collection of numbers, and each of these numbers you want to multiply by 2. How would you approach it? You would go over each of these values, multiply by 2 and replace the old value with a new one. Later on, you want to perform another calculation on the same collection - add 10 to each of these numbers. If you follow this carefully, you should notice that both operations have a common task which is an iteration. The only difference is the calculation on each of this numbers. As it turns out, there is a lot of patterns when dealing with collections, and by using iteration with functions, we can bring flexibility to these operations.
Let's analyze this example:
iex> array = [1, 2, 3, 4]
[1, 2, 3, 4]
iex> Enum.map array, fn num -> num * 2 end
[2, 4, 6, 8]
We are storing [1, 2, 3, 4]
list inside of array
variable. On each of the list elements, we want to perform multiplication by 2. We can do that with Enum.map/2
function (what it means will clear out soon, once we introduce modules and named function), which takes the list as the first argument. Look carefully what we pass as the second argument. It's anonymous function! Enum.map/2
function iterate over each element of array
and pass it to the anonymous function call. As a result, each element of the collection will be passed as num
in the anonymous function call:
fn num -> num * 2 end
In Elixir, functions are just values, and that's why it's possible to pass them as function arguments.
Function execution scope
Since in Elixir functions are just values, we can return them as a result of function execution:
iex> func = fn -> fn -> 2 + 2 end end
#Function<20.128620087/0 in :erl_eval.expr/5>
iex> func.()
#Function<20.128620087/0 in :erl_eval.expr/5>
iex> func.().()
4
Let's take a closer look at this example. When we call:
func.()
we get in return:
#Function<20.128620087/0 in :erl_eval.expr/5>
which is function with following body:
fn -> 2 + 2 end
Since it's a function we can execute it as well:
func.().()
With this last call, we were able to evaluate the inner function which returns 2 + 2
calculation.
How about scenario where both (outer and inner) functions takes arguments and nested function rely on its caller? Let's break it down:
iex> say_hello = fn first_name -> ( fn last_name -> "Hello #{first_name} #{last_name}" end ) end
#Function<6.128620087/1 in :erl_eval.expr/5>
iex> say_hello.("Mister").("Robot")
"Hello Mister Robot"
The outer function is called with one argument, since it expects to be invoked this way. The inner function is called with one argument as well for the same reason. We already know that function must be called with as many arguments as its declaration suggest. What's new is this:
"Hello #{first_name} #{last_name}"
Although the inner function expects one argument, it interpolates its own last_name
argument and first_name
from the outer function. Why? Because functions in Elixir remember their original environment and therefore they automatically carry bindings of variables in the scope in which they are defined. In other words, nested function has access to variables declared in its parent function.
Short notation
There is also a cleaner and shorter notation for anonymous functions. The following notations are syntactically equal:
typical syntax:
iex> func = fn (a, b) -> a * b end #Function<12.128620087/2 in :erl_eval.expr/5> iex> func.(4,5) 20
short syntax:
iex> func = &(&1 * &2) &:erlang.*/2 iex> func.(4,5) 20
The shorter syntax of an anonymous function consists of three parts:
The
&
operator converts the expression that follows into a function.Body of the function is enclosed in operator, for example:
(), [], %{}, {}, ""
.&1
,&2
placeholders correspond to the first and second parameter of the function. Keep in mind that function can take more than 2 parameters.
Applying these three parts to our example &(&1 * &2)
, we can observe the following things:
Our function starts with
&
.We enclosed the whole operation in
()
operator, which will return the result of&1 * &2
calculation.&1
holds value4
, and&2
holds value5
.
Guard clause
In Elixir, you can create function with guard clause:
iex> func = fn
...(1)> a, b when a > b -> "#{a} is bigger than #{b}"
...(1)> a, b when b > a -> "#{b} is bigger than #{a}"
...(1)> a, b when a == b -> "#{a} is equal #{b}"
...(1)> end
#Function<12.128620087/2 in :erl_eval.expr/5>
iex> func.(5,6)
"6 is bigger than 5"
iex> func.(6,5)
"6 is bigger than 5"
iex> func.(5,5)
"5 is equal 5"
Guard clause brings control flow to functions. When you want to invoke function declaration depending on arguments values, you can use when
clause with a condition. The first matching condition will perform its corresponding body and return from a function. In our example there are three different conditions:
a > b
,b > a
,a == b
.
Each of these conditions refers to different function implementation. For example, this function execution:
func.(5,6)
matches with this conditional implementation:
a, b when b > a -> "#{b} is bigger than #{a}"
Value comparison is not the only thing you can do in guard clause:
iex> func = fn
...(1)> a when is_float(a) -> "#{a} is a float"
...(1)> a when is_integer(a) -> "#{a} is a integer"
...(1)> end
#Function<6.128620087/1 in :erl_eval.expr/5>
iex> func.(2.0)
"2.0 is a float"
iex> func.(2)
"2 is a integer"
You can find a full list of allowed guard clauses here.