我正在学习Elixir,并想知道为什么它有两种类型的函数定义:
def
模块中定义的函数,使用myfunction(param1, param2)
fn
定义的匿名函数,使用myfn.(param1, param2)
只有第二种函数似乎是第一类函数,可以作为参数传递给其他函数。模块中定义的函数需要包含在fn
中。为了简单起见,有一些看起来像otherfunction(myfunction(&1, &2))
的语法糖,但为什么首先需要它?为什么我们不能otherfunction(myfunction))
?是否只允许调用模块函数而不像Ruby那样括号?它似乎从Erlang继承了这个特性,它也有模块功能和实用程序,它实际上来自Erlang VM如何在内部工作?
有两种类型的功能并从一种类型转换为另一种类型以便将它们传递给其他功能有什么好处?调用函数有两种不同的符号有什么好处吗?
答案 0 :(得分:350)
只是为了澄清命名,它们都是功能。一个是命名函数,另一个是匿名函数。但你是对的,他们的工作方式有所不同,我将说明他们为什么这样工作。
让我们从第二个fn
开始。 fn
是一个闭包,类似于Ruby中的lambda
。我们可以按如下方式创建它:
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。
现在,我们来谈谈命名函数:
def hello(x, y) do
x + y
end
正如所料,他们有一个名字,他们也可以收到一些论点。但是,它们不是封闭的:
x = 1
def hello(y) do
x + y
end
这段代码无法编译,因为每次看到def
时,都会得到一个空的变量范围。这是他们之间的重要区别。我特别喜欢这样一个事实,即每个命名函数都以一个干净的平板开始,并且你不会将不同范围的变量全部混合在一起。你有一个明确的界限。
我们可以将上面命名的hello函数检索为匿名函数。你自己提到了:
other_function(&hello(&1))
然后你问,为什么我不能简单地将其作为hello
传递给其他语言?这是因为Elixir中的函数由名称和 arity标识。因此,期望两个参数的函数与期望三个参数的函数不同,即使它们具有相同的名称。因此,如果我们只是通过hello
,我们就不知道您实际上是指hello
。有两个,三个或四个论点的那个?这与我们无法使用具有不同arities的子句创建匿名函数的原因完全相同。
自Elixir v0.10.1以来,我们有一个捕获命名函数的语法:
&hello/1
这将捕获带有arity 1的本地命名函数hello。在整个语言及其文档中,以hello/1
语法识别函数是很常见的。
这也是Elixir使用点来调用匿名函数的原因。由于你不能简单地将hello
作为函数传递,而是需要明确地捕获它,命名函数和匿名函数之间存在明显区别,并且调用每个函数的独特语法使得所有内容都更加明确(Lispers)由于Lisp 1与Lisp 2的讨论,我会对此很熟悉。
总的来说,这就是为什么我们有两个功能以及为什么它们表现不同的原因。
答案 1 :(得分:17)
我不知道这会对其他人有多大帮助,但我最终围绕这个概念的方式是认识到灵丹妙药的功能并非功能。
灵丹妙药中的一切都是表达。所以
MyModule.my_function(foo)
不是函数,而是通过执行my_function
中的代码返回的表达式。实际上只有一种方法可以获得&#34;功能&#34;您可以作为参数传递,也就是使用匿名函数表示法。
很容易引用fn或&amp;符号作为函数指针,但它实际上更多。这是周围环境的封闭。
如果你问自己:
我是否需要在此位置执行环境或数据值?
如果你需要执行使用fn,那么大多数困难都会变得很多 清晰。
答案 2 :(得分:11)
我可能错了,因为没有人提到它,但我的印象也是因为这个原因也是能够在没有括号的情况下调用函数的红宝石遗产。
Arity显然参与其中,但是让我们搁置一段时间并使用没有参数的函数。在javascript这样的语言中,括号是必需的,很容易区分传递函数作为参数和调用函数。只有在使用括号时才调用它。
my_function // argument
(function() {}) // argument
my_function() // function is called
(function() {})() // function is called
正如您所看到的,命名与否并没有太大的区别。但是elixir和ruby允许你在没有括号的情况下调用函数。这是我个人喜欢的设计选择,但它有这种副作用你不能只使用没有括号的名称,因为它可能意味着你想要调用该功能。这就是&
的用途。如果您将arity appart保留一秒钟,则使用&
添加函数名称意味着您明确要将此函数用作参数,而不是此函数返回的内容。
现在匿名函数有点不同,因为它主要用作参数。这又是一个设计选择,但它背后的理性是它主要由迭代器使用的函数作为参数。显然,您不需要使用&
,因为默认情况下它们已被视为参数。这是他们的目的。
现在最后一个问题是有时你必须在代码中调用它们,因为它们并不总是与迭代器类函数一起使用,或者你可能自己编写迭代器。对于小故事,因为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)
同样,这是一个设计选择。现在elixir不是面向对象的,因此请不要使用第一种形式。我不能代表José,但看起来第二种形式在elixir中使用,因为它仍然看起来像一个带有额外字符的函数调用。它足够接近函数调用。
我没有考虑所有的优点和缺点,但看起来在两种语言中,只要你为匿名函数强制使用括号,你就可以使用括号。看起来好像是:
强制性括号VS略有不同的符号
在这两种情况下,您都会出现异常,因为两者的行为都不同。由于存在差异,您可以将其显而易见并使用不同的符号。在大多数情况下,强制性括号看起来很自然,但如果事情没有按计划进行,则会非常混乱。
你走了。现在这可能不是世界上最好的解释,因为我简化了大部分细节。其中大部分都是设计选择,我试图给出一个理由而不判断它们。我喜欢长生不老药,我喜欢红宝石,我喜欢没有括号的函数调用,但是和你一样,我发现偶尔会有很多错误的后果。
在灵丹妙药中,它只是这个额外的点,而在红宝石中,你可以拥有块。块是惊人的,我很惊讶你可以用块做多少,但它们只在你只需要一个匿名函数时工作,这是最后一个参数。那么既然你应该能够处理其他场景,那么整个方法/ lambda / proc / block就会出现混乱。
无论如何......这超出了范围。
答案 3 :(得分:10)
我从未明白为什么对此的解释如此复杂。
这真的只是一个非常小的区别,结合了Ruby风格的“没有parens的功能执行”的现实。
比较
def fun1(x, y) do
x + y
end
要:
fun2 = fn
x, y -> x + y
end
虽然这些都只是标识符......
fun1
是一个标识符,用于描述使用def
定义的命名函数。fun2
是描述变量的标识符(恰好包含对函数的引用)。当您在其他表达式中看到fun1
或fun2
时,请考虑这意味着什么?在评估该表达式时,您是调用引用的函数还是仅引用内存中的值?
在编译时没有好的方法可以知道。 Ruby可以很好地反省变量名称空间,以确定变量绑定是否在某个时间点隐藏了一个函数。被编译的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提示是非常清楚的。想象一下,如果每个变量去引用必须检查一个函数?或者,想象一下,总是推断变量解引用是使用函数需要什么样的英雄主义?
答案 4 :(得分:8)
这是一篇关于此行为的优秀博文:link
如果模块包含:
fac(0) when N > 0 -> 1; fac(N) -> N* fac(N-1).
你不能只将它剪切并粘贴到shell中并获得相同的效果 结果
这是因为Erlang中存在错误。 Erlang中的模块是序列 of FORMS 。 Erlang shell评估一系列的 表达式。在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)。
如果你这样离开,预计将花费未来二十年 你的生活解释了原因。
答案 5 :(得分:2)
Elixir具有功能的可选括号,包括arity为0的功能。让我们看一个为什么它使单独的调用语法很重要的示例:
defmodule Insanity do
def dive(), do: fn() -> 1 end
end
Insanity.dive
# #Function<0.16121902/0 in Insanity.dive/0>
Insanity.dive()
# #Function<0.16121902/0 in Insanity.dive/0>
Insanity.dive.()
# 1
Insanity.dive().()
# 1
在不区分两种类型的函数的情况下,我们无法说出Insanity.dive
的含义:获取函数本身,对其进行调用,或者也对生成的匿名函数进行调用。
答案 6 :(得分:0)
只有第二种函数似乎是第一类函数,可以作为参数传递给其他函数。模块中定义的函数需要包装在fn中。为了简单起见,有一些看起来像
otherfunction(myfunction(&1, &2))
的语法糖,但为什么首先需要它呢?为什么我们不能otherfunction(myfunction))
?
您可以执行otherfunction(&myfunction/2)
由于elixir可以执行不带括号的函数(如myfunction
),因此使用otherfunction(myfunction))
会尝试执行myfunction/0
。
因此,您需要使用捕获运算符并指定函数,包括arity,因为您可以使用相同名称的不同函数。因此,&myfunction/2
。
答案 7 :(得分:0)
fn ->
语法用于使用匿名函数。做var。()只是告诉elixir,我希望您使用带有功能的var并运行它,而不是将var称为仅持有该函数的东西。
Elixir有一个通用的模式,在该模式中,我们不是根据函数内部的逻辑来查看应如何执行操作,而是根据所具有的输入类型对不同的函数进行模式匹配。我认为这就是为什么我们在function_name/1
的意义上以礼节来指代事物。
习惯进行速记函数定义(func(&1)等)有点奇怪,但是在尝试使用管道或保持代码简洁时很方便。