【问题标题】:Function not defined during piping, except when returning an anonymous function在管道期间未定义函数,除非返回匿名函数
【发布时间】:2016-12-03 09:35:11
【问题描述】:

可能是一个非常基本的 Elixir 问题,

我想将偶数从 1 加到 10 并输出到 IO.puts

首先我尝试这样做:

1..10
|> Enum.filter(fn (x) -> rem(x, 2) == 0 end)
|> Enum.sum
|> IO.puts

按预期工作。


然后我尝试在模块中定义该函数:

defmodule Test do
  def is_even(x) do
    rem(x, 2) == 0
  end
end

1..10
|> Enum.filter(Test.is_even)
|> Enum.sum
|> IO.puts

但这给了我以下编译错误:

** (UndefinedFunctionError) undefined function: Test.is_even/0
    Test.is_even()
    tmp/src.exs:8: (file)
    (elixir) src/elixir_lexical.erl:17: :elixir_lexical.run/3
    (elixir) lib/code.ex:316: Code.require_file/2

为什么它应该(按我的意图)寻找 is_even/1 却寻找 is_even/0?


我不明白为什么会这样,尤其是这样做之后:

defmodule Test do
  def hello(x) do
    IO.puts(x)
  end
end

Test.hello("Hello World!")

工作得很好。


我也刚刚发现这是可行的:

defmodule Test do
  def is_even() do
    fn (x) -> rem(x, 2) == 0 end
  end
end

1..10
|> Enum.filter(Test.is_even)
|> Enum.sum
|> IO.puts

为什么它使用函数的返回作为函数使用而不是使用函数本身?

有没有办法让这个工作而不必在函数内部返回匿名函数?

【问题讨论】:

    标签: elixir


    【解决方案1】:

    与一些常用语言不同,Elixir(和 Erlang 以及可能所有基于 BEAM 的语言)允许您使用相同的名称定义多个函数。 Elixir 试图尝试将函数调用与与您调用它的定义相同的定义相匹配。

    Arity 是函数接受的参数数量。例如,以下函数的元数为 0

    def foo() do
      IO.puts "foo"
    end
    

    这个函数

    def bar(x) do
      IO.puts "bar"
    end
    

    有一个 1,即使参数未被使用。

    使用你的模块

    defmodule Test do
      def is_even(x) do
        rem(x, 2) == 0
      end
    end
    

    您已经定义了函数is_even,其元数为 1。

    当您尝试在您的链中调用您的函数Test.is_even 时,您调用它时就好像它的元数为 0。如果您想指定您的函数应该是元数 1,您可以使用以下命令符号,&Test.is_even/1。所以你的例子会变成

    1..10
    |> Enum.filter(&Test.is_even/1)
    |> Enum.sum
    |> IO.puts
    

    【讨论】:

      【解决方案2】:
      defmodule Test do
        def is_even(x) do
          rem(x, 2) == 0
        end
      end
      
      1..10
      |> Enum.filter(Test.is_even)
      |> Enum.sum
      |> IO.puts
      

      |> Enum.filter(Test.is_even) 是由于管道运算符的工作方式而不起作用的行。管道运算符会将表达式的结果放在它之前 (1..10) 作为 Enum.filter/2 的第一个参数,但它不会将它作为 Test.is_even/1 的参数,因为它在 filter 内部。

      那行就像写:Enum.filter 1..10, Test.is_even(),没有为Test.is_even 传递任何参数,所以它寻找一个 0-arity 函数但它找不到。

      您最后一个示例中的is_even/0 有效,因为该函数返回一个匿名函数,与您给出的第一个示例相同。

      【讨论】:

      • 这解释了为什么他们试图做的事情不起作用,而不是如何让它真正起作用。
      • 我回答了第一个问题。
      【解决方案3】:

      是的,你想做的事情是可能的,而且很容易。您正在尝试像传递 Elixir 中的其他值一样传递函数,这是可能的。例如:

      f = &IO.puts/1
      

      这会将IO.puts/1 分配给变量f,它可以像其他值一样使用。每当引用这样的函数时,您必须包含函数的数量,在您的情况下是 1,因为您的函数需要一个参数。调用函数时,arity 是隐式的,因为您传入了许多参数,但是当您以这种方式引用函数时,您必须手动指定 arity,因为您的 is_even 函数可能有多个版本,具有不同的 arity 和 VM不知道你想要哪个。

      您的代码应如下所示:

      defmodule Test do
        def is_even(x) do
          rem(x, 2) == 0
        end
      end
      
      1..10
      |> Enum.filter(&Test.is_even/1)
      |> Enum.sum
      |> IO.puts
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-09-08
        • 2013-03-29
        • 2014-03-13
        • 1970-01-01
        • 1970-01-01
        • 2019-08-09
        • 2011-12-29
        • 2019-09-12
        相关资源
        最近更新 更多