Elixir Language

Functions

Anonymous Functions

In Elixir, a common practice is to use anonymous functions. Creating an anonymous function is simple:

iex(1)> my_func = fn x -> x * 2 end
#Function<6.52032458/1 in :erl_eval.expr/5>

The general syntax is:

fn args -> output end

For readability, you may put parenthesis around the arguments:

iex(2)> my_func = fn (x, y) -> x*y end
#Function<12.52032458/2 in :erl_eval.expr/5>

To invoke an anonymous function, call it by the assigned name and add . between the name and arguments.

iex(3)>my_func.(7, 5)
35

It is possible to declare anonymous functions without arguments:

iex(4)> my_func2 = fn -> IO.puts "hello there" end
iex(5)> my_func2.()
hello there
:ok

Using the capture operator

To make anonymous functions more concise you can use the capture operator &. For example, instead of:

iex(5)> my_func = fn (x) -> x*x*x end

You can write:

iex(6)> my_func = &(&1*&1*&1)

With multiple parameters, use the number corresponding to each argument, counting from 1:

iex(7)> my_func = fn (x, y) -> x + y end

iex(8)> my_func = &(&1 + &2)   # &1 stands for x and &2 stands for y

iex(9)> my_func.(4, 5)
9

Multiple bodies

An anonymous function can also have multiple bodies (as a result of pattern matching):

my_func = fn
  param1 -> do_this
  param2 -> do_that
end

When you call a function with multiple bodies Elixir attempts to match the parameters you have provided with the proper function body.

Keyword lists as function parameters

Use keyword lists for ‘options’-style parameters that contains multiple key-value pairs:

def myfunc(arg1, opts \\ []) do
  # Function body
end

We can call the function above like so:

iex> myfunc "hello", pizza: true, soda: false

which is equivalent to:

iex> myfunc("hello", [pizza: true, soda: false])

The argument values are available as opts.pizza and opts.soda respectively.
Alternatively, you could use atoms: opts[:pizza] and opts[:soda].

Named Functions & Private Functions

Named Functions

defmodule Math do
    # one way
    def add(a, b) do
        a + b
    end

    # another way
    def subtract(a, b), do: a - b
end

iex> Math.add(2, 3)
5
:ok
iex> Math.subtract(5, 2)
3
:ok

Private Functions

defmodule Math do
    def sum(a, b) do
        add(a, b)
    end

    # Private Function
    defp add(a, b) do
        a + b
    end
end

iex> Math.add(2, 3)
** (UndefinedFunctionError) undefined function Math.add/2
Math.add(3, 4)
iex> Math.sum(2, 3)
5

Pattern Matching

Elixir matches a function call to its body based on the value of its arguments.

defmodule Math do
    def factorial(0): do: 1
    def factorial(n): do: n * factorial(n - 1)
end

Here, factorial of positive numbers matches the second clause, while factorial(0) matches the first. (ignoring negative numbers for the sake of simplicity). Elixir tries to match the functions from top to bottom. If the second function is written above the first, we will an unexpected result as it goes to an endless recursion. Because factorial(0) matches to factorial(n)

Guard clauses

Guard clauses enables us to check the arguments before executing the function. Guard clauses are usually preferred to if and cond due to their readability, and to make a certain optimization technique easier for the compiler. The first function definition where all guards match is executed.

Here is an example implementation of the factorial function using guards and pattern matching.

defmodule Math do
    def factorial(0), do: 1
    def factorial(n) when n > 0: do: n * factorial(n - 1)
end

The first pattern matches if (and only if) the argument is 0. If the argument is not 0, the pattern match fails and the next function below is checked.

That second function definition has a guard clause: when n > 0. This means that this function only matches if the argument n is greater than 0. After all, the mathematical factorial function is not defined for negative integers.

If neither function definition (including their pattern matching and guard clauses) match, a FunctionClauseError will be raised. This happens for this function when we pass a negative number as the argument, since it is not defined for negative numbers.

Note that this FunctionClauseError itself, is not a mistake. Returning -1 or 0 or some other “error value” as is common in some other languages would hide the fact that you called an undefined function, hiding the source of the error, possibly creating a huge painful bug for a future developer.

Default Parameters

You can pass default parameters to any named function using the syntax: param \\ value:

defmodule Example do
    def func(p1, p2 \\ 2) do
        IO.inspect [p1, p2]
    end
end

Example.func("a")    # => ["a", 2]
Example.func("b", 4) # => ["b", 4]

Capture functions

Use & to capture functions from other modules. You can use the captured functions directly as function parameters or within anonymous functions.

Enum.map(list, fn(x) -> String.capitalize(x) end)

Can be made more concise using &:

Enum.map(list, &String.capitalize(&1))

Capturing functions without passing any arguments require you to explicitly specify its arity, e.g. &String.capitalize/1:

defmodule Bob do
  def say(message, f \\ &String.capitalize/1) do
    f.(message)
  end
end

This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow