在Scala中传递函数时的下划线用法

时间:2018-05-31 17:47:18

标签: scala

我正在关注本教程,我看到了这段代码:

def totalCostWithDiscountFunctionParameter(donutType: String)(quantity: Int)(f: Double => Double): Double = {
  println(s"Calculating total cost for $quantity $donutType")
  val totalCost = 2.50 * quantity
  println("inside totalCostWithDiscountFunctionParameter")
  f(totalCost)
}

def applyDiscount(totalCost: Double): Double = {
  val discount = 2 // assume you fetch discount from database
  totalCost - discount
}

println(s"Total cost of 5 Glazed Donuts with discount function = ${totalCostWithDiscountFunctionParameter("Glazed Donut")(5)(applyDiscount(_))}")

_中的applyDiscount(_)有什么意义?我也可以删除它,只需按名称applyDiscount传递函数,代码就可以了。下划线有什么意义?它是一回事吗?

1 个答案:

答案 0 :(得分:1)

applyDiscount(_)placeholder syntax for anonymous functions。这扩展到:

x => applyDiscount(x)

applyDiscount方法传递给函数时,即:

totalCostWithDiscountFunctionParameter("5")(applyDiscount)

然后scalac将执行eta-expansion,这意味着将方法转换为函数值。

这些是否具有完全相同的语义?关闭,但不完全。请考虑What are all the uses of an underscore in Scala?中给出的以下示例(稍微修改,完全归功于@Owen的示例和提供的答案)

trait PlaceholderExample {
  def process[A](f: A => Unit)

  val set: Set[_ => Unit]

  set.foreach(process) // Error 
  set.foreach(process(_)) // No Error
}

第二次成功时编译时的第一个错误,为什么会这样?让我们看看用-Xprint:typer编译的代码:

λ scalac -Xprint:typer Test.scala
FooBar.scala:11: error: polymorphic expression cannot be instantiated to expected type;
 found   : [A](A => Unit) => Unit
 required: (Function1[_, Unit]) => ?
  set.foreach(process) // Error
              ^
[[syntax trees at end of                     typer]]
package yuval.tests {
  abstract trait PlaceholderExample extends scala.AnyRef {
    def /*PlaceholderExample*/$init$(): Unit = {
      ()
    };
    def process[A](f: A => Unit): Unit;
    <stable> <accessor> val set: Set[Function1[_, Unit]];
    PlaceholderExample.this.set.foreach[U](process);
    PlaceholderExample.this.set.foreach[Unit](((x$1: Function1[_, Unit]) => PlaceholderExample.this.process[_$1](x$1)))
  }
}

您首先看到的是编译错误,因为scalac无法将方法process从多态方法扩展为单态函数。这意味着,当scalac尝试将A绑定到实际类型时,它会查找类型(_ => Unit) => ?,但会失败,因为_(存在性)不是类型。

另一方面,扩展为x => process(x)的第二个示例进行编译,因为当scalac遇到没有显式类型注释的lambda表达式时,它会查看方法签名的类型(在我们的例子中,{{ 1}}期望foreach,它被归类为类型)并成功地将类型参数绑定到_ => Unit

因此,在大多数情况下,你会发现这两个是同构的(尽管它们确实不是)(甚至IntelliJ建议我可以写一个而不是另一个),但是有一些边缘情况可以区分一个另一个。