为什么Elixir有两种function?

我正在学习Elixir,并想知道为什么它有两种types的函数定义:

  • def定义的函数,用myfunction(param1, param2)
  • fn定义的匿名函数,使用myfn.(param1, param2)调用myfn.(param1, param2)

只有第二种function似乎是第一类对象,可以作为parameter passing给其他function。 在模块中定义的函数需要封装在fn 。 有一些语法糖,看起来像otherfunction(myfunction(&1, &2)) ,以便使这种简单,但为什么它是必要的第一位? 为什么我们不能做otherfunction(myfunction)) ? 是不是只允许调用模块的function没有像在Ruby中的括号? 似乎从Erlanginheritance了这个也具有模块function和乐趣的特性,那么Erlang VM究竟是如何工作的呢?

它有两种types的function,并从一种types转换为另一种,以便将它们传递给其他function? 有两种不同的符号可以调用函数吗?

只是为了澄清命名,它们都是function。 一个是命名函数,另一个是匿名函数。 但是你是对的,他们的工作有些不同,我要说明他们为什么这样工作。

我们从第二个fnfn是一个闭包,类似于Ruby中的lambda 。 我们可以创build它如下:

 x = 1 fun = fn y -> x + y end fun.(2) #=> 3 

一个函数也可以有多个子句:

 x = 1 fun = fn y when y < 0 -> x - y y -> x + y end fun.(2) #=> 3 fun.(-2) #=> 3 

现在,让我们尝试一些不同的东西。 让我们尝试定义期望不同的参数个数的不同的子句:

 fn x, y -> x + y x -> x end ** (SyntaxError) cannot mix clauses with different arities in function definition 

不好了! 我们得到一个错误! 我们不能混合期望不同数量的论点的子句。 一个函数总是有一个固定的arity。

现在,我们来谈谈命名的function:

 def hello(x, y) do x + y end 

正如所料,他们有一个名字,他们也可以收到一些论据。 但是,他们不是封闭的:

 x = 1 def hello(y) do x + y end 

这段代码将无法编译,因为每次看到def ,都会得到一个空的variables作用域。 这是他们之间的重要区别。 我特别喜欢每个命名函数都以一个干净的版本开始的事实,而且不会将不同范围的variables混合在一起。 你有一个清晰的界限。

我们可以将上面的命名的hello函数作为一个匿名函数来获取。 你提到你自己:

 other_function(&hello(&1)) 

然后你问,为什么我不能像其他语言一样简单地通过它? 这是因为Elixir的function是通过名字名字来标识的。 所以一个期望两个参数的函数是一个与期望三个函数不同的函数,即使它们有相同的名字。 所以,如果我们简单地通过hello ,我们不知道你实际上是什么意思。 有两个,三个或四个论点的那个? 这就是为什么我们不能用不同的子句创build一个匿名函数的原因。

由于Elixir v0.10.1,我们有一个语法来捕获命名函数:

 &hello/1 

这将捕获本地命名函数hello与arity 1.在整个语言及其文档中,识别hello/1语法中的函数是非常常见的。

这也是为什么Elixir使用点来调用匿名函数。 既然你不能简单地把hello作为一个函数来传递,而是需要显式地捕获它,那么在命名函数和匿名函数之间有一个自然的区别,并且每个函数的调用都有一个明确的语法,这使得一切都变得更加明确了(Lispers会熟悉这是由于Lisp 1与Lisp 2的讨论)。

总的来说,这就是为什么我们有两个职能,为什么他们的行为有所不同。

我不知道这会对其他人有多大的用处,但是我最终将这个概念包裹起来的方式是意识到elixir函数不是函数。

万能药中的一切都是一种expression。 所以

 MyModule.my_function(foo) 

不是函数,而是通过执行my_function的代码返回的expression式。 实际上,只有一种方法可以得到一个可以作为parameter passing的“函数”,也就是使用匿名函数表示法。

将fn或&notation作为一个函数指针是很有诱惑力的,但实际上它更多。 这是周围环境的closures。

如果你问自己:

在这个地方我需要一个执行环境还是数据值?

如果你需要执行fn,那么大部分的困难就会变得更加清晰。

我理解为什么需要&f / 1的解释,尽pipe有一种方法可以说“任何一个函数的所有函数”,而其余部分则在下面进行。

即使需要&f / 1来引用函数本身,我也不明白为什么也需要使用“。(args)”变体来调用该函数。 这是因为潜在的名字遮蔽或其他原因?

这是Elixir一生中必须重复解释的东西。

http://joearms.github.io/2013/05/31/a-week-with-elixir.html

我可能是错的,因为没有人提到它,但我也有一个印象,就是这个原因也是能够不带括号地调用函数的ruby遗产。

Arity显然涉及到,但让我们暂时搁置一下,使用没有参数的函数。 在像javascript这样的语言中,括号是强制性的,很容易将作为parameter passing函数和调用函数进行区别。 只有在使用方括号时才能调用它。

 my_function // argument (function() {}) // argument my_function() // function is called (function() {})() // function is called 

正如你所看到的,命名或不命名没有太大的区别。 但是elixir和ruby允许你在没有括号的情况下调用函数。 这是我个人喜欢的deviseselect,但它有这个副作用,你不能使用没有括号的名字,因为这可能意味着你想调用该函数。 这是&目的。 如果你离开了appart一秒钟,用&表示你的函数名称意味着你明确地想要使用这个函数作为参数,而不是这个函数返回什么。

现在匿名函数有点不同,主要用作参数。 再次,这是一个deviseselect,但其背后的理由是它主要是由函数作为参数的迭代器类function。 所以显然你不需要使用&因为默认情况下它们已经被认为是参数了。 这是他们的目的。

现在最后一个问题是,有时你必须在代码中调用它们,因为它们并不总是用于迭代器types的函数,或者你可能自己编写一个迭代器。 对于小故事来说,由于ruby是面向对象的,主要的做法是在对象上使用call方法。 这样,你可以保持非强制性的括号行为一致。

 my_lambda.call my_lambda.call() my_lambda_with_arguments.call :h2g2, 42 my_lambda_with_arguments.call(:h2g2, 42) 

现在有人想出了一个基本上看起来像没有名字的方法的快捷方式。

 my_lambda.() my_lambda_with_arguments.(:h2g2, 42) 

再次,这是一个deviseselect。 现在仙丹不是面向对象的,因此不要使用第一种forms。 我不能为José说话,但看起来第二种forms是用在elixir中的,因为它看起来像一个带有额外字符的函数调用。 它足够接近函数调用。

我没有考虑所有的优点和缺点,但是只要你对匿名函数强制使用括号,就可以用括号括起来。 它似乎是这样的:

强制括号VS稍有不同的表示法

在这两种情况下,你都会犯例外,因为你们的行为都不一样。 既然有区别,你不妨把它弄明白,去做不同的记法。 在大多数情况下,强制性的括号看起来很自然,但是如果事情没有按计划进行的话,会非常混乱。

干得好。 现在这可能不是世界上最好的解释,因为我简化了大部分细节。 也大多数是deviseselect,我试图给他们没有判断他们的理由。 我喜欢长生不老药,我喜欢ruby,我喜欢没有括号的函数调用,但是和你一样,我偶尔发现后果相当不明确。

而在长生不老药,它只是这个额外的点,而在ruby,你有块上面。 块是惊人的,我很惊讶你可以做多less块,但只有当你只需要一个匿名函数,这是最后一个参数。 然后,因为你应该能够处理其他情况下,整个方法/ lambda / proc /块混淆。

无论如何…这是超出范围。

我从来不明白为什么这个解释太复杂了。

这实际上只是一个特别小的区别,结合了Ruby风格的“没有parens的函数执行”的实际情况。

比较:

 def fun1(x, y) do x + y end 

至:

 fun2 = fn x, y -> x + y end 

虽然这两个只是标识符…

  • fun1是描述用def定义的命名函数的标识符。
  • fun2是描述variables的标识符(恰好包含对函数的引用)。

考虑一下当你在其他表情中看到fun1或者fun2时是什么意思? 在评估这个expression式的时候,你会调用被引用的函数吗?或者你只是引用一个内存不足的值?

在编译时没有什么好的方法可以知道。 Ruby具有内省variables名称空间的优点,以查明variables绑定是否在某个时间点影响了某个函数。 正在编译的Elixir不能真正做到这一点。 这就是点符号的作用,它告诉Elixir它应该包含一个函数引用,并且应该被调用。

这真的很难。 想象一下,没有一个点符号。 考虑这个代码:

 val = 5 if :rand.uniform < 0.5 do val = fn -> 5 end end IO.puts val # Does this work? IO.puts val.() # Or maybe this? 

鉴于上面的代码,我想这很清楚你为什么要给Elixir提示。 想象一下,如果每个variables去引用必须检查一个函数? 或者,想象一下总是推断variables解引用是使用一个函数需要哪些英雄?

有一个关于这种行为的优秀的博客文章: 链接

两种types的function

如果一个模块包含这个:

 fac(0) when N > 0 -> 1; fac(N) -> N* fac(N-1). 

你不能把它剪切并粘贴到shell中并得到相同的结果。

这是因为Erlang有一个bug。 Erlang中的模块是FORMS的序列。 Erlang shell评估一系列的expression式 。 在Erlang FORMS不是EXPRESSIONS

 double(X) -> 2*X. in an Erlang module is a FORM Double = fun(X) -> 2*X end. in the shell is an EXPRESSION 

这两个是不一样的。 这个愚蠢的地方一直是Erlang的,但是我们没有注意到,我们学会了和它一起生活。

在调用fn

 iex> f = fn(x) -> 2 * x end #Function<erl_eval.6.17052888> iex> f.(10) 20 

在学校,我学会了通过写f(10)来调用函数而不是f。(10) – 这是一个真正的函数,其名称如Shell.f(10)(这是shell中定义的函数)shell部分隐含的,所以它应该被称为f(10)。

如果你这样离开它,期望花费你生命的下一个二十年来解释为什么。

只有第二种function似乎是第一类对象,可以作为parameter passing给其他function。 在模块中定义的函数需要封装在fn中。 有一些语法糖,看起来像otherfunction(myfunction(&1, &2)) ,以便使这种简单,但为什么它是必要的第一位? 为什么我们不能做otherfunction(myfunction))

你可以做otherfunction(&myfunction/2)

由于elixir可以执行没有方括号(如myfunction ),使用otherfunction(myfunction))函数,它会尝试执行myfunction/0

因此,您需要使用捕获运算符并指定包括arity在内的函数,因为可以使用相同名称的不同函数。 因此, &myfunction/2