我正在学习Elixir,并想知道为什么它有两种类型的函数定义:
def,调用usingmyfunction(param1, param2)fn,调用usingmyfn.(param1, param2)只有第二种函数似乎是第一类函数,可以作为参数传递给其他函数.模块中定义的函数需要包装在一个fn.有一些语法糖似乎是otherfunction(myfunction(&1, &2))为了使这容易,但为什么它首先是必要的?我们为什么不能这样做otherfunction(myfunction))?是否只允许调用模块函数而不像Ruby那样括号?它似乎从Erlang继承了这个特性,它也具有模块功能和实用程序,所以它实际上来自Erlang VM如何在内部工作?
有两种类型的函数并从一种类型转换为另一种类型以便将它们传递给其他函数有什么好处?呼叫功能有两种不同的符号吗?
Jos*_*lim 372
只是为了澄清命名,它们都是功能.一个是命名函数,另一个是匿名函数.但你是对的,他们的工作方式有所不同,我将说明他们为什么这样工作.
让我们从第二个开始吧fn.fn是一个闭包,类似于lambdaRuby中的闭包.我们可以按如下方式创建它:
x = 1
fun = fn y -> x + y end
fun.(2) #=> 3
Run Code Online (Sandbox Code Playgroud)
一个函数也可以有多个子句:
x = 1
fun = fn
y when y < 0 -> x - y
y -> x + y
end
fun.(2) #=> 3
fun.(-2) #=> 3
Run Code Online (Sandbox Code Playgroud)
现在,让我们尝试不同的东西.让我们尝试定义期望不同数量的参数的不同子句:
fn
x, y -> x + y
x -> x
end
** (SyntaxError) cannot mix clauses with different arities in function definition
Run Code Online (Sandbox Code Playgroud)
不好了!我们收到错误!我们不能混合需要不同数量参数的子句.一个函数总是有一个固定的arity.
现在,我们来谈谈命名函数:
def hello(x, y) do
x + y
end
Run Code Online (Sandbox Code Playgroud)
正如所料,他们有一个名字,他们也可以收到一些论点.但是,它们不是闭包:
x = 1
def hello(y) do
x + y
end
Run Code Online (Sandbox Code Playgroud)
此代码将无法编译,因为每次看到a时def,都会得到一个空的变量范围.这是他们之间的重要区别.我特别喜欢这样一个事实:每个命名函数都以一个干净的平板开始,你不会将不同范围的变量全部混合在一起.你有一个明确的界限.
我们可以将上面命名的hello函数检索为匿名函数.你自己提到过:
other_function(&hello(&1))
Run Code Online (Sandbox Code Playgroud)
然后你问,为什么我不能hello像其他语言一样简单地传递它?那是因为Elixir中的函数是通过名称和 arity 来标识的.因此,期望两个参数的函数与期望三个参数的函数不同,即使它们具有相同的名称.所以如果我们简单地通过hello,我们就不知道hello你的意思.有两个,三个或四个论点的那个?这与我们无法使用具有不同arities的子句创建匿名函数的原因完全相同.
自Elixir v0.10.1以来,我们有一个捕获命名函数的语法:
&hello/1
Run Code Online (Sandbox Code Playgroud)
这将使用arity 1捕获本地命名函数hello.在整个语言及其文档中,识别此hello/1语法中的函数是很常见的.
这也是Elixir使用点来调用匿名函数的原因.因为你不能简单地hello作为一个函数传递,而是你需要明确地捕获它,命名函数和匿名函数之间存在明显的区别,并且调用每个函数的独特语法使得所有内容都更加明确(Lispers会熟悉这一点)由于Lisp 1 vs. Lisp 2讨论).
总的来说,这就是为什么我们有两个功能以及为什么它们表现不同的原因.
Fre*_*Dog 17
我不知道这会对其他人有多大帮助,但我最终围绕这个概念的方式是认识到elixir函数不是函数.
灵丹妙药中的一切都是一种表达方式.所以
MyModule.my_function(foo)
Run Code Online (Sandbox Code Playgroud)
不是函数,而是执行代码返回的表达式my_function.实际上只有一种方法可以获得一个可以作为参数传递的"函数",即使用匿名函数表示法.
将fn或&符号称为函数指针很有吸引力,但它实际上更多.这是周围环境的封闭.
如果你问自己:
我需要这个位置的执行环境或数据值吗?
如果你需要执行使用fn,那么大多数困难会变得更加清晰.
小智 13
我永远不明白为什么对此的解释如此复杂.
它实际上只是一个非常小的区别,结合了Ruby风格的"没有parens的函数执行"的现实.
相比:
def fun1(x, y) do
x + y
end
Run Code Online (Sandbox Code Playgroud)
至:
fun2 = fn
x, y -> x + y
end
Run Code Online (Sandbox Code Playgroud)
虽然这两者都只是标识符......
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?
Run Code Online (Sandbox Code Playgroud)
鉴于上面的代码,我认为为什么你必须给Elixir提示是非常清楚的.想象一下,如果每个变量去引用必须检查一个函数?或者,想象一下,总是推断变量解引用是使用函数需要什么样的英雄主义?
Mig*_*g R 12
我可能是错的,因为没有人提到它,但我也有这样的印象,其原因也是能够在没有括号的情况下调用函数的红宝石遗产.
Arity显然参与其中,但是暂时放下它并使用没有参数的函数.在javascript这样的语言中,括号是必需的,很容易区分传递函数作为参数和调用函数.只有在使用括号时才调用它.
my_function // argument
(function() {}) // argument
my_function() // function is called
(function() {})() // function is called
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,命名与否并没有太大的区别.但是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)
Run Code Online (Sandbox Code Playgroud)
现在有人想出了一个基本上看起来像一个没有名字的方法的快捷方式.
my_lambda.()
my_lambda_with_arguments.(:h2g2, 42)
Run Code Online (Sandbox Code Playgroud)
同样,这是一个设计选择.现在elixir不是面向对象的,因此请不要使用第一种形式.我不能代表José,但看起来第二种形式在elixir中使用,因为它仍然看起来像一个带有额外字符的函数调用.它足够接近函数调用.
我没有考虑所有的优点和缺点,但看起来在两种语言中,只要你为匿名函数强制使用括号,你就可以使用括号.看起来好像是:
强制性括号VS略有不同的表示法
在这两种情况下,您都会出现异常,因为两者的行为都不同.由于存在差异,您可以将其显而易见并使用不同的符号.在大多数情况下,强制性括号看起来很自然,但是当事情没有按计划进行时会非常混乱.
干得好.现在这可能不是世界上最好的解释,因为我简化了大部分细节.其中大部分都是设计选择,我试图给出一个理由而不判断它们.我喜欢长生不老药,我喜欢红宝石,我喜欢没有括号的函数调用,但是和你一样,我发现偶尔会产生很多错误的后果.
在长生不老药中,它只是这个额外的点,而在红宝石中你有块.块是惊人的,我很惊讶你可以用块做多少,但它们只在你只需要一个匿名函数时工作,这是最后一个参数.然后,因为你应该能够处理其他场景,所以整个方法/ lambda/proc/block混淆了.
无论如何......这超出了范围.
有关于此行为的优秀博客文章:链接
如果模块包含:
Run Code Online (Sandbox Code Playgroud)fac(0) when N > 0 -> 1; fac(N) -> N* fac(N-1).您不能将其剪切并粘贴到shell中并获得相同的结果.
这是因为Erlang中存在一个错误.Erlang中的模块是FORMS的序列.Erlang shell评估一系列 EXPRESSIONS.在Erlang中,FORMS不是EXPRESSIONS.
Run Code Online (Sandbox Code Playgroud)double(X) -> 2*X. in an Erlang module is a FORM Double = fun(X) -> 2*X end. in the shell is an EXPRESSION两者不一样.这种愚蠢一直是Erlang的永恒,但我们没有注意到它,我们学会了忍受它.
fnRun Code Online (Sandbox Code Playgroud)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).
如果你这样离开,那么你可以在接下来的二十年中解释原因.