为什么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。 一个是命名函数,另一个是匿名函数。 但是你是对的,他们的工作有些不同,我要说明他们为什么这样工作。
我们从第二个fn
。 fn
是一个闭包,类似于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一生中必须重复解释的东西。
我可能是错的,因为没有人提到它,但我也有一个印象,就是这个原因也是能够不带括号地调用函数的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
。