Want to learn?

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

Documentation

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:

  1. The & operator converts the expression that follows into a function.

  2. Body of the function is enclosed in operator, for example: (), [], %{}, {}, "".

  3. &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:

  1. Our function starts with &.

  2. We enclosed the whole operation in () operator, which will return the result of &1 * &2 calculation.

  3. &1 holds value 4, and &2 holds value 5.

Guard clause

Documentation

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.

Check our latest product - it's based on our experience of managing over 50-people strong company. The tool we're missing as a small company and not an enterprise.

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
Top

Contact us

* Required fields

The controller of your personal data provided via this contact form is Prograils sp. z o.o., with a registered seat at Sczanieckiej 9A/10, 60-215 PoznaƄ. Your personal data will be processed in order to respond to your inquiries and for our marketing purposes (e.g. when you ask us for our post-development, maintenance or ad hoc engagements for your app). You have the rights to: access your personal data, rectify or erase your personal data, restrict the processing of your personal data, data portability and to object to the processing of your personal data. Learn more.

Notice

We do not track you online. We use only session cookies and anonymous identifiers for the purposes specified in the cookie policy. No third-party trackers.

I understand
Elo Mordo!Elo Mordo!