【问题标题】:By-name parameter vs anonymous function按名称参数与匿名函数
【发布时间】:2016-08-23 07:51:46
【问题描述】:

目前尚不清楚的是,在惰性求值和其他好处(如果有的话)方面,与匿名函数相比,名称参数的优势是什么:

def func1(a: => Int)
def func2(a: () => Int)

什么时候用第一个,什么时候用第二个?

这不是What's the difference between => , ()=>, and Unit=>的副本

【问题讨论】:

标签: scala


【解决方案1】:

这两种格式可以互换使用,但在某些情况下我们只能使用其中一种主题。

我们举例说明,假设我们需要定义一个有两个参数的case类:

{
  .
  .
  .
  type Action = () => Unit;

  case class WorkItem(time : Int, action : Action);
  .
  .
  .

}

我们可以看到,WorkItem 类的第二个参数有一个 Action 类型。

如果我们尝试将此参数替换为其他格式=>

case class WorkItem1(time : Int, s : => Unit)编译器会显示错误信息:

这一行有多个标记:

`val' 参数不能是按名称调用的按名称调用参数 创建:() ⇒

所以我们看到格式 ()=> 更通用,我们可以用它来定义类型,作为类字段或方法参数,在另一边 => 格式可以用作方法参数,但不能用作类字段。

省略空参数列表 () 的按名称类型仅是 允许参数。没有名称变量或 按名称字段。

【讨论】:

    【解决方案2】:

    一个示例可能会非常全面地介绍这些差异。

    假设您想在 Scala 中编写您自己版本的名副其实的 while 循环。我知道,我知道……在 Scala 中使用while?但这与函数式编程无关,这是一个很好地展示该主题的示例。所以和我在一起。我们将调用我们自己的版本whyle。此外,我们希望使用 Scala 的内置 while 来实现它。为了实现这一点,我们可以使我们的 whyle 构造递归。此外,我们将添加@tailrec 注释以确保我们的实现可以用作内置while 的实际替代品。这是第一次尝试:

    @scala.annotation.tailrec
    def whyle(predicate: () => Boolean)(block: () => Unit): Unit = {
      if (predicate()) {
        block()
        whyle(predicate)(block)
      }
    }
    

    让我们看看它是如何工作的。我们可以将参数化代码块传递给whyle。第一个是predicate 参数化函数。第二个是block 参数化函数。我们将如何使用它?

    我们想要让我们的最终用户使用whyle,就像您使用while 控制结构一样:

    // Using the vanilla 'while'
    var i = 0
    while(i < args.length) {
      println(args(i))
      i += 1
    }
    

    但由于我们的代码块是参数化的whyle 循环的最终用户必须添加一些丑陋的语法糖才能使其工作:

    // Ouch, our whyle is hideous
    var i = 0
    whyle( () => i < args.length) { () =>
      println(args(i))
      i += 1
    }
    

    所以。看来,如果我们希望最终用户能够以更熟悉的原生风格调用我们的 whyle 循环,我们将需要使用 无参数 函数。但是我们遇到了一个非常大的问题。一旦你使用了无参数函数,你就不能再吃蛋糕了。你只能吃你的蛋糕。看:

    @scala.annotation.tailrec
    def whyle(predicate: => Boolean)(block: => Unit): Unit = {
      if (predicate) {
        block
        whyle(predicate)(block)  // !!! THIS DOESN'T WORK LIKE YOU THINK !!!
      }
    }
    

    哇。现在用户可以按照他们期望的方式调用我们的whyle 循环......但是我们的实现没有任何意义。您无法同时调用无参数函数和将函数本身作为值传递。你只能调用它。这就是我说的只吃你的蛋糕的意思。你也不能拥有。因此,我们的递归实现现在退出了窗口。它只适用于参数化的函数,不幸的是非常丑陋。

    此时我们可能会想作弊。我们可以重写 whyle 循环以使用 Scala 的内置 while

    def whyle(pred: => Boolean)(block: => Unit): Unit = while(pred)(block)
    

    现在我们可以像使用while 一样使用我们的whyle,因为我们只需要能够吃我们的蛋糕……我们也不需要拥有它。

    var i = 0
    whyle(i < args.length) {
      println(args(i))
      i += 1
    }
    

    但是我们作弊了!实际上,这是一种拥有我们自己的尾部优化版本的 while 循环的方法:

    def whyle(predicate: => Boolean)(block: => Unit): Unit = {
      @tailrec
      def whyle_internal(predicate2: () => Boolean)(block2: () => Unit): Unit = {
        if (predicate2()) {
          block2()
          whyle_internal(predicate2)(block2)
        }
      }
      whyle_internal(predicate _)(block _)
    }
    

    你能弄清楚我们刚刚做了什么吗?我们在 inner 函数中有我们原来的(但丑陋的)参数化函数。我们用一个函数包装了它,该函数将无参数函数作为参数。然后它调用内部函数并将无参数函数转换为参数化函数(通过将它们转换为部分应用函数)。

    让我们试试看它是否有效:

    var i = 0
    whyle(i < args.length) {
      println(args(i))
      i += 1
    }
    

    确实如此!

    谢天谢地,因为在 Scala 中我们有闭包,所以我们可以大扫除:

    def whyle(predicate: => Boolean)(block: => Unit): Unit = {
      @tailrec
      def whyle_internal: Unit = {
        if (predicate) {
          block
          whyle_internal
        }
      }
      whyle_internal
    }
    

    酷。无论如何,这些是无参数函数和参数化函数之间的一些非常大的区别。我希望这能给你一些想法!

    【讨论】:

    • 非常好的答案 ;)
    • 但是我不同意你最后的代码(用闭包重构)。实际上,在执行precidateblock 时,它只会被评估一次,因为它们是按名称调用的参数。你怎么看?
    • @Mik378 非常感谢您的积极反馈!我在gist.github.com/afazio/4a30e05d5ec8aaab0b7b 创建了一个要点,其中包含一个测试whyle 循环的最新版本的快速脚本。让我知道你的想法......再次感谢你!
    • 但我必须承认,我应该清理我的答案......措辞有点草率。 ://
    • 其实你是对的。我认为block 只会被评估一次,即第一次调用。但实际上,block 指定的所有表达式每次都会被求值。所以你的代码很好;)
    【解决方案3】:

    懒惰在这两种情况下是相同的,但有细微的差别。考虑:

    def generateInt(): Int = { ... }
    def byFunc(a: () => Int) { ... }
    def byName(a: => Int) { ... }
    
    // you can pass method without
    // generating additional anonymous function
    byFunc(generateInt)
    
    // but both of the below are the same
    // i.e. additional anonymous function is generated
    byName(generateInt)
    byName(() => generateInt())
    

    但是,名称调用的函数对于制作 DSL 很有用。例如:

    def measured(block: ⇒ Unit): Long = {
      val startTime = System.currentTimeMillis()
      block
      System.currentTimeMillis() - startTime
    }
    
    Long timeTaken = measured {
      // any code here you like to measure
      // written just as there were no "measured" around
    }
    

    【讨论】:

      【解决方案4】:
        def func1(a: => Int) {
          val b = a // b is of type Int, and it`s value is the result of evaluation of a
        }
      
      def func2(a: () => Int) {
          val b = a // b is of type Function0 (is a reference to function a)
        }
      

      【讨论】:

      • 那又怎样,有什么区别?我也可以做 val b = a()。
      【解决方案5】:

      如果您想按名称传递Int 作为参数,您应该使用第一个函数定义。 如果您希望参数是返回 Int 的无参数函数,请使用第二个定义。

      【讨论】:

      • 和有什么区别?
      • def func1(a: =&gt; Int)def func2(a: =&gt; String) 有什么区别?参数类型不同。就像你的例子一样。
      • 这很明显,但不能回答问题。
      猜你喜欢
      • 1970-01-01
      • 2011-12-29
      • 1970-01-01
      • 2017-07-31
      • 1970-01-01
      • 2021-02-19
      • 2011-03-02
      • 2019-02-10
      相关资源
      最近更新 更多