Anonymous Functions
Anonymous functions allow us to store and pass executable code around as if it was an integer or a string.
To define an anonymous function, use the fn arguments -> body end syntax, where ‘arguments’ are comma-separated and ‘body’ is regular Elixir code.
Such function can be assigned to a variable, passed as an argument to another function, etc.
add = fn a, b -> a + b end
add
To call an anonymous function assigned to a variable f, use f.(arguments). The dot before the opening bracket differentiates calling an anonymous function from a regular one. This distinction avoids ambiguity - when you see is_atom(:foo), you know Kernel.is_atom is called, not an anonymous function assigned to is_atom variable.
# 💡 Try removing the dot from the call
add.(2, 3)
You can check if a value is a function using is_function/1:
is_function(add)
You can also check if a function has a specific arity (number of arguments) using is_function/2:
# 💡 Try changing 2 to a different number
is_function(add, 2)
Returning values
Now that we know how to define and call anonymous functions, let’s understand how they return values.
In Elixir, there’s no return keyword. Instead, the value returned is just the last expression in the function body. This means each function returns something - if there’s nothing sensible to return, we usually return :ok.
# 💡 Let's make inspect_and_add return :ok
inspect_and_add = fn a, b ->
IO.inspect({a, b})
a + b
end
inspect_and_add.(2, 3)
Closures
Similar to case/2, we can pattern match on the arguments of anonymous functions as well as define multiple clauses and guards. This is typically referred to as closures, as they close over their scope.
a = 3
increment_by_a = fn x -> x + a end
# 💡 Try assigning 4 to variable 'a',
# after the function is defined and before it's called
# Does it affect the function?
increment_by_a.(2)
Clauses and guards
Similar to case/2, anonymous functions can pattern match on arguments and define multiple clauses with guards. The number of arguments in each clause must be the same, otherwise an error is raised.
# 💡 Try adding another clause with three arguments: x, y and z
f = fn
x, y when x > 0 -> x + y
a, b -> a * b
end
# 💡 Let's change 1 to -1
f.(1, 3)
The capture operator
For creating short, simple functions, you can use the capture operator &.
Then, we can use &1, &2, &3 etc to refer to the first, second, third argument etc.
# This is equivalent to fn a, b -> a + b end
add = & &1 + &2
add.(2, 3)
You can also use the capture operator to wrap function calls:
split_by_comma = &String.split(&1, ",")
split_by_comma.("1,2,3")
Capturing regular function
The capture operator has another use: converting a regular function to an anonymous one, for example to pass it as an argument.
Sometimes we may want to convert a regular function to an anonymous one, for example to pass it as an argument.
We could write it as &function(&1, &2, ...), but there’s a shorthand for that: &function/arity, where ‘arity’ stands for the number of arguments.
# 💡 Try also &String.contains?/2
string_split = &String.split/2
string_split
The captured function behaves just like any other anonymous function:
is_function(string_split)
string_split.("hello world", " ")