0

Underline _ cannot work for some scenario in following code:

case class Item
(
  para: Int
)

val a = List(1, 2)
val b = List(Item(1), Item(2))

a foreach {
  perA => println(perA * 2) // Line A: ok
}

a foreach {
  println(_) // Line B: ok
}

a foreach {
  println(_ * 2) // Line C: not ok
}

b foreach {
  perB => println(perB.para) // line D: ok
}

b foreach {
  println(_.para) // line E: not ok
}

Could someone explain for me about line C & line E, thanks.

atline
  • 28,355
  • 16
  • 77
  • 113
  • Here is a complementary question: [usage of underscore in lambda functions](https://stackoverflow.com/questions/7673545/usage-of-in-scala-lambda-functions/7673633#7673633). There, the OP wondered why `a.groupBy(_)` is not expanded into `a.groupBy(x => x)`. In your case, `x` was inserted "too deep inside", in the linked question, the `x` was inserted "too far outside". – Andrey Tyukin Feb 10 '18 at 10:56
  • Here is [another very similar question](https://stackoverflow.com/questions/5259006/underscore-in-named-arguments), but instead of a `_.member`, a named parameter assignment `paramName = _` got in the way. It contains a very consise rule (quote Seth Tisue): "_ always picks the tightest non-degenerate scope it can" *(I'm trying to understand how this is a duplicate. It feels like it should be, but I can't find an exact match. )* – Andrey Tyukin Feb 10 '18 at 11:02

1 Answers1

3

TL;DR: the argument of the lambda can be inserted at an unexpected position. Instead of taking

a foreach { println(_.blah) }

and building

a foreach { x => println(x.blah) }

out of it, the compiler instead builds

a foreach { println( x => x.blah ) }

and then fails to derive the type for the argument x.


A) This is the most explicit way to write down the lambda, the only way to make it clearer would be to add a type:

a foreach { perA => println(perA * 2) }

is the same as

a foreach { (x: Int) => println(x * 2) }

This should obviously work.

B) This works because it's one way to write down the function automatically generated from println. It is equivalent to any of the six variants below:

a foreach { (x: Int) => println(x) }
a foreach { x => println(x) }
a foreach { println(_) }
a foreach { println _ }
a foreach { println }
a foreach println

C) Because of the * 2, this here

a foreach { println(_ * 2) }

can no longer be considered just println(_). Instead, it is interpreted as

a foreach { println( { (x: ??!) => x * 2 } ) }

and since println takes no function-valued arguments, it cannot determine what the type of x is supposed to be, and exits with an error.

D) Is essentially the same as A, it works, I hope it's clear.

E) Is a variation of C, but this time, the type-checker isn't looking for something with method *(i: Int), but instead it's looking for something with a member para.

This here:

b foreach { println(_.para) }

is again interpreted as a foreach with a function which ignores elements of b and returns the constant value of the expression println(_.para), that is:

b foreach { println( { (x: ??!) => x.para } ) }

Again, the inner expression println( { (x: ??!) => x.para } ) does not make any sense, because println does not expect function-valued arguments (it can handle Any, but it's not enough to derive the type of x).

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93