【问题标题】:Why do we need a function "capture operator" in Elixir?为什么我们在 Elixir 中需要一个函数“捕获运算符”?
【发布时间】:2017-10-31 03:53:12
【问题描述】:

有人能解释一下为什么在 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,如果函数已经可以在没有所述操作符的情况下传递,我们还需要使用捕获操作符吗?函数的普通旧名称不是已经“捕获”它了吗?

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

基本上,我不了解 & 捕获运算符的用例以及它提供的优势。

【问题讨论】:

  • 也许是这样,您可以通过 /1 /2 等后缀符号包含参数的数量,否则会被解释为除法。在函数式语言中,当参数的数量和类型发生变化时,函数可以是完全不同的声明。

标签: elixir


【解决方案1】:

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

请注意,不推荐使用不带() 的零参数函数,但仍然有效。我想一旦删除它,编译器可能会做出选择,但即便如此也可能有其他原因导致它无法工作。

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

# 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 

一个变量一次只能有一个绑定。因此,它所引用的内容没有歧义。但是,命名函数可以有多个具有不同参数的子句。因此,如果我们只提供函数名称,那么我们所指的是哪个子句就会有歧义。

【讨论】:

    【解决方案2】:

    在其他语言中,主要是 JavaScript,它不需要它,因为传递对函数的引用是有效的,但它不适用于 Elixir,如果你用 Elixir 引用一个函数,它会默认调用它。

    您可以通过在终端中运行重新编译来测试它,并立即看到一个巨大的错误出现,因为您试图调用一个函数,认为您正在引用一个函数。

    所以而不是:

    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 在上面的示例中采用了一个参数。

    【讨论】:

      【解决方案3】:

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

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

      对于长生不老药,可以在没有括号的情况下应用函数,并且也可以热切地评估参数。所以很难确定是要应用get_env 还是传递get_env 作为函数参数

      【讨论】:

        【解决方案4】:

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

        Reference 给出了 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))
        

        就我个人而言,我更喜欢尽可能避免捕获。虽然缩短了表达方式,但当表达复杂时,很容易忽略意图并误解表达方式。

        【讨论】:

          猜你喜欢
          • 2016-05-28
          • 2022-01-14
          • 2011-04-14
          • 2018-09-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-04-09
          • 1970-01-01
          相关资源
          最近更新 更多