为什么Elixir有两种功能?

loading...


237

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

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

只有第二种函数似乎是第一类对象,可以作为参数传递给其他函数 . 模块中定义的函数需要包装在 fn 中 . 有一些语法糖看起来像 otherfunction(myfunction(&1, &2)) ,以使这容易,但为什么它首先是必要的?为什么我们不能做 otherfunction(myfunction)) ?它只是允许调用模块函数而不像Ruby那样括号吗?它似乎从Erlang继承了这个特性,它也有模块功能和实用程序,它实际上来自Erlang VM如何在内部工作?

有两种类型的函数并从一种类型转换为另一种类型以便将它们传递给其他函数有什么好处?呼叫功能有两种不同的符号吗?

6回答

  • 0

    只是为了澄清命名,它们都是功能 . 一个是命名函数,另一个是匿名函数 . 但你是对的,他们的工作方式有所不同,我将说明他们为什么这样工作 .

    让我们从第二个开始, 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中的函数由名称 and arity标识 . 因此,期望两个参数的函数与期望三个参数的函数不同,即使它们具有相同的名称 . 所以如果我们简单地传递 hello ,我们就不知道你实际上是指哪个 hello . 有两个,三个或四个论点的那个?这与我们无法使用具有不同arities的子句创建匿名函数的原因完全相同 .

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

    &hello/1
    

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

    这也是Elixir使用点来调用匿名函数的原因 . 因为你不能简单地将 hello 作为一个函数传递,而是你需要明确地捕获它,命名函数和匿名函数之间存在明显的区别,并且调用每个函数的独特语法使得所有内容都更加明确(Lispers会熟悉)这是由于Lisp 1 vs. Lisp 2的讨论) .

    总的来说,这就是为什么我们有两个功能以及为什么它们表现不同的原因 .


  • 17

    我不知道这会对其他人有多大帮助,但是我最终围绕这个概念的方式是认识到elixir函数不是函数 .

    灵丹妙药中的一切都是表达 . 所以

    MyModule.my_function(foo)
    

    不是函数,而是通过执行 my_function 中的代码返回的表达式 . 实际上只有一种方法可以将"Function"作为参数传递,即使用匿名函数表示法 .

    将fn或&符号称为函数指针很有吸引力,但它实际上更多 . 这是周围环境的封闭 .

    如果你问自己:

    我需要在这个位置执行环境或数据值吗?

    如果你需要执行使用fn,那么大多数困难会变得更加清晰 .


  • 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混淆了 .

    无论如何......这超出了范围 .


  • 330

    我永远不明白为什么解释这么复杂 .

    它实际上只是一个非常小的区别,结合了Ruby风格的“没有parens的函数执行”的现实 .

    相比:

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

    至:

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

    虽然这两者都只是标识符......

    • fun1 是一个标识符,用于描述使用 def 定义的命名函数 .

    • fun2 是描述变量的标识符(恰好包含对函数的引用) .

    当你在其他表达式中看到 fun1fun2 时,请考虑一下这意味着什么?在评估该表达式时,您是调用引用的函数还是只引用内存中的值?

    在编译时没有好的方法可以知道 . 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提示是非常明确的 . 想象一下,如果每个变量去引用都必须检查一个函数?或者,想象一下,总是推断变量解引用是使用函数需要什么样的英雄主义?


  • 8

    有一篇关于这种行为的优秀博文:link

    两种功能

    如果一个模块包含这个:fac(0)当N> 0 - > 1时;
    fac(N) - > N * fac(N-1) .
    您不能将其剪切并粘贴到shell中并获得相同的结果 . 这是因为Erlang中存在一个错误 . Erlang中的模块是FORMS的序列 . Erlang shell评估一系列EXPRESSIONS . 在Erlang中,FORMS不是EXPRESSIONS . double(X) - > 2 * X.在Erlang模块中是一个FORM

    Double = fun(X) - > 2 * X end . 在shell中是一个表达式
    两者不一样 . 这有点愚蠢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) . 如果你这样离开,那么你可以在接下来的二十年中解释原因 .


  • 8

    只有第二种函数似乎是第一类函数,可以作为参数传递给其他函数 . 模块中定义的函数需要包装在fn中 . 有一些语法糖,看起来像其他功能(myfunction(&1,&2)),以使这容易,但为什么有必要在一开始?为什么我们不能只做其他功能(myfunction))?

    你可以做 otherfunction(&myfunction/2)

    由于elixir可以在没有括号的情况下执行函数(如 myfunction ),因此使用 otherfunction(myfunction)) 会尝试执行 myfunction/0 .

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

loading...

评论

暂时没有评论!