为什么我们需要一个功能"捕获运算符"在Elixir?

时间:2017-05-30 15:23:53

标签: elixir

有人可以解释为什么在Elixir中需要"捕获运算符",表示为&符号前缀?在其他语言中,它不是:

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

在Elixir中的效果相同,显然是:

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    

那么为什么,例如here,如果函数已经可以在没有所述运算符的情况下传递,我们是否需要使用捕获运算符?并不是函数的普通旧名称&#34; capture&#34;它?

iex(10)> Enum.map([1, 2, 3], double)
[2, 4, 6]

基本上我不了解&amp; amp;的用例。捕获运算符及其提供的优势。

4 个答案:

答案 0 :(得分:3)

在上面的示例中,您已将匿名函数fn x -> x + x end绑定到变量double。捕获运算符在您传递命名函数时使用。保存/传递一个命名函数,你需要一种方法来表明它是一个命名函数,而不是一个变量。这是您使用capture&amp; name / arity语法的地方。

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

在这种情况下,如何解决get_env。它是对get_env / 0的调用,还是对get_env / 1的引用?对于匿名函数,double是变量绑定,double.(1)是绑定到变量double的函数的调用。

请注意,不使用()调用零arity函数已被弃用,但仍然有效。我想,一旦删除它,那么也许编译器可以做出选择,但即使这样,也可能有其他原因导致它仍然不起作用。

另一个原因。让我们假设,例如,我们支持使用命名函数名称。我们怎么能支持这个:

# 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 

变量一次只能有一个绑定。因此,它的引用没有含糊不清。但是,命名函数可能具有不同arity的多个原因。因此,如果我们只提供函数名称,那么我们所引用的子句也存在歧义。

答案 1 :(得分:1)

在其他语言(主要是JavaScript)中,不需要使用它,因为传递对函数的引用是可行的,但对于Elixir而言,这种方式不起作用,如果您使用Elixir引用函数,则默认情况下将调用它。

您可以通过在终端中运行recompile进行测试,并立即观察到出现巨大错误,因为您尝试调用函数以为您在引用函数。

所以代替:

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

您要这样做:

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

&符表示我即将传递对函数的引用。我传递给的函数参考是mirror_row,然后关键是我有了/1,这意味着如果我定义了多个版本的称为mirror_row的函数,我特别希望接受一个参数,arity为一,我这样做是因为mirror_row在上面的示例中接受了一个参数。

答案 2 :(得分:1)

可能只是因为Erlang设计。在Erlang中,那些不使用特殊格式fun Module:Function/Arity传递的函数参数在Erlang VM中被解释为原子,因此会导致异常。

因此,其原因可能是使其更易于转换Elixir代码以使其兼容在BEAM中运行。而且,Erlang的决定要求笨拙的形式可能希望它更具体,并帮助编译器更容易地发现错误的Arity传递函数的错误。

对于灵丹妙药,可以在不使用方括号的情况下应用函数,并且也急切地评估参数。因此,很难确定是要应用get_env还是将get_env用作函数参数

答案 3 :(得分:0)

Elixir中的捕获运算符(&)与其他语言中的引用运算符相同,但具有扩展功能。

参考给出了Elixir对象的唯一标识符和描述:数据类型,结构(结构等同于Elixir OOP对象)和函数。

参考文献区分对象的“内容”和对象本身。为了能够区分何时调用函数和何时将其作为参数传递给其他函数使用,使用了引用。

Elixir是一种功能语言,这意味着它将计算视为对数学函数的评估,并使用不可变变量。

简化后,函数可以像变量一样传递,并且所有结果都只是从一个函数推到另一个函数,这应该会带来一些意想不到的好处,并在使用时导致巨大的爆发力。

# 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))

就个人而言,我宁愿尽可能避免被俘获。尽管它缩短了表达式,但是当它变得复杂时,很容易忽略意图并误解了表达式。