Reputation: 24898
Can someone explain why, in Elixir, the "capture operator", denoted as an ampersand prefix, is needed? In other languages it isn't :
Python 3.6.0 |Anaconda 4.3.0 (64-bit)| (default, Dec 23 2016, 12:22:00)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
>>> def double(x):
... return(x + x)
...
>>> double(2)
4
>>> dbl = double
>>> dbl(2)
4
This works the same in Elixir, apparently:
iex(2)> double = fn x -> x + x end
#Function<6.118419387/1 in :erl_eval.expr/5>
iex(3)> double.(2)
4
iex(4)> dbl = double
#Function<6.118419387/1 in :erl_eval.expr/5>
iex(5)> dbl.(2)
4
So why, for example here, do we ever need to use a capture operator if the function can already be passed around without said operator? Doesn't the plain old name of the function already "capture" it?
iex(10)> Enum.map([1, 2, 3], double)
[2, 4, 6]
Basically I don't understand the use case for the & capture operator and what advantages it affords.
Upvotes: 7
Views: 2467
Reputation: 4507
In your example above, you have bound the anonymous function fn x -> x + x end
to the variable double
. The capture operator is used when you're passing a named function. T save/pass a named function, you need a way to indicate that it's a named function, and not a variable. This is where you use the capture &name/arity
syntax.
defmodule FunWithFuns do
def get_env, do: Application.get_all_env(:my_app)
def get_env(item), do: Application.get_env(:my_app, item)
def some_function do
IO.inspect get_env
Enum.map([:item1, :item2], get_env)
end
end
How do you resolve the get_env
in this case? Is it a call to get_env/0
, or is it a reference to get_env/1
? In the case of anonymous functions, double
is the variable binding and double.(1)
is the invocation of the function bound to the variable double
.
Note that calling zero-arity functions without the ()
has been deprecated but still works. I suppose that once that is removed perhaps the compiler could make the choice, but even then there may be other reasons why it would not work.
Another reason: let's assume, for example, that we did support using the named function name. How could we support this:
# contrived example
defmodule MoreFunWithFuns do
def fun1, do: :something_stateful
def fun1(x), do: x + 1
def higher(list, fun) do
cond do
is_function(fun, 0) -> fun.() |> process_state
is_function(fun, 1) -> Enum.map(list, fun) |> process_state
end
end
def run(list) do
higher(list, fun1) # which fun1 here?
end
end
A variable can only have one binding at time. So, there is no ambiguity as to what it's referencing. However, a named function can have multiple clauses with different arities. So, if we provide just the function name, there is ambiguity as to which clause we are referring.
Upvotes: 8
Reputation: 11
Capture operator (&) in Elixir is same as reference operator in other languages, with extended functionality.
Reference gives an unique identifier and description of an Elixir objects: data types, structures (structure is Elixir equivalent of OOP object) and functions.
Reference differentiates between the 'content' of the object and the object itself. To be able to distinguish when function is called from when it is passed as parameter to other functions to use, reference is used.
Elixir is a functional language, which means it treats computation as the evaluation of mathematical functions and uses immutable variables.
Simplified, functions can be passed around like variables and all results are just pushed from one function to another, which should give some unexpected advantages and leads to a huge explosion of power when using.
# Function can be defined as a named element of module/structure,
# anonymous function, or function/functional variable
# define functional variable by matching it to anonymous function:
# (double is a reference of anonymous function f(x) = x + x)
double = fn x -> x + x end
# execute a function linked to a functional variable:
double.(2) # calculate f(2) = 2 + 2
# give a reference to a functional variable:
# (reference can be used to call the function with various parameters)
&double.(&1)
# Enum.each(enum, fun/1) is a very common function used in elixir
# It will iterate through all elements of an enumerable collection
# and call the fun/1 with the element as a parameter
# Parameters are enumerable collection and reference to a function
# with one parameter
# Lets double all elements in cleanest way of writing:
Enum.each([1, 2, 3], fn x -> x + x end)
# same as previous line, written in shorter way using capture
# &1 means reference to first parameter
Enum.each(1..3, &(&1 + &1))
# same functionality, using previously defined functional variable double
Enum.each(1..3, fn x -> double.(x) end)
# same as previous line, written in shorter way using capture
Enum.each(1..3, &double.(&1))
# same functionality, but prints out the results
Enum.each(1..3, fn x -> IO.puts double.(x) end)
# same as previous line, written in shorter way using capture
Enum.each(1..3, &IO.puts double.(&1))
Personally, i prefer to avoid capture when possible. Although it shortens the expressions, when it gets complicated, it is easy to overlook the intention and misunderstand the expression.
Upvotes: 1
Reputation: 100
It could be just because of Erlang design. In Erlang, those function argument passed without special form fun Module:Function/Arity
are interpreted as atoms in Erlang VM and thus it causes an exception.
So, the reason could be to make it easier to transform Elixir code to be compatible to be run in BEAM. And the decison of Erlang require that clumsy form might want it to be more specific and help compiler easier to find bug of passing function with the wrong arity.
And for elixir, function can be applied without bracket and arguments are eagerly evaluated as well. So it's hard to determine if you want to apply get_env
or pass get_env
as function argument
Upvotes: 1
Reputation: 15413
In other languages, mainly JavaScript, its not needed because passing a reference to a function works, but it does not work that way with Elixir, if you refer a function with Elixir it will invoke it by default.
You can test that out by running recompile in your terminal and instantly watch a huge error appear because you tried to call a function thinking you were referencing a function.
So instead of:
def build_grid(%Identicon.Image{hex: hex} = image) do
hex
|> Enum.chunk(3)
|> Enum.map(mirror_row)
end
def mirror_row(row) do
# [145, 46, 200]
[first, second | _tail] = row
# [145, 46, 200, 46, 145]
row ++ [second, first]
end
you want to do this:
def build_grid(%Identicon.Image{hex: hex} = image) do
hex
|> Enum.chunk(3)
|> Enum.map(&mirror_row/1)
end
def mirror_row(row) do
# [145, 46, 200]
[first, second | _tail] = row
# [145, 46, 200, 46, 145]
row ++ [second, first]
end
The ampersand says I am about to pass a reference to a function. The function I am passing reference to is mirror_row
and then critically at the end I have /1
which means if I have multiple versions of the function called mirror_row
defined, I specifically want the one that takes one argument, an arity of one and I am doing that because mirror_row
takes one argument in the example above.
Upvotes: 3