Underscore usage when passing a function in Scala

2019-08-22 01:16发布

问题:

I am following this tutorial and I see this code:

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(_))}")

What is the point of the _ in applyDiscount(_)? I can also remove it and just pass the function by name like this applyDiscount and the code works. What is the point of the underscore? Is it the same thing?

回答1:

applyDiscount(_) is placeholder syntax for anonymous functions. This expands to:

x => applyDiscount(x)

When you pass the applyDiscount method to the function, i.e:

totalCostWithDiscountFunctionParameter("5")(applyDiscount)

Then scalac will perform eta-expansion, which means turning a method to a function value.

Do these have exactly the same semantics? Close, but not quite. Consider the following example given in What are all the uses of an underscore in Scala? (slightly modified, full credit to @Owen for the example and the provided answer)

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

  val set: Set[_ => Unit]

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

The first errors at compile time while the second succeeds, why is that? Let's look at the code compiled with -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)))
  }
}

First thing you see is a compilation error because scalac was unable to eta-expand the method process from a polymorphic method to a monomorphic function. Meaning, when scalac attempts to bind A to an actual type, it looks for a type (_ => Unit) => ? but fails because _ (an existential) is not a type.

On the other hand, the second example which is expanded to x => process(x) compiles because when scalac encounters a lambda expression with no explicit type annotation, it looks at the type of the method signature (in our case, foreach expects a _ => Unit, which is classified as a type) and successfully binds the type argument to process.

Thus, in most cases you'll find that the two are isomorphic (although they really aren't)(even IntelliJ recommended that I can write one instead of the other), but there are edge cases which does distinguish one from the other.



标签: scala