Having drilled, I can say unhelpfully that it is defined in terms of syntax productions.
I think the rule of thumb about parens is unhelpful. (Because it is often repeated but has settled nothing in the popular imagination.)
The underscore expands at the enclosing expression, as opposed to “simple expressions” like infix ops x * y
and selections x.y
.
scala> List(true).map(!_)
val res0: List[Boolean] = List(false)
scala> List(true).exists(if (_) false else true)
val res1: Boolean = false
Everything is an expression, such as the condition:
scala> List(true).exists(if (!_) false else true)
^
error: missing parameter type for expanded function ((<x$1: error>) => x$1.unary_$bang)
where the enclosing expession is the condition and not the if.
Similarly
scala> List(42).map((_, 27))
val res3: List[(Int, Int)] = List((42,27))
scala> List(42).map((_ + 1, 27))
^
error: missing parameter type for expanded function ((<x$1: error>) => x$1.$plus(1))
because the elements in parens are expressions. (The infix op is a simple expression, but in a comma-separated list in parens, it’s an expression.)
I think the notion of “simple expressions” is helpful just to remember why common idioms work, like
strings.find(_.length > 3)
or
scala> Option(42).map(_ + 1 > 50)
val res6: Option[Boolean] = Some(false)
scala> List((i: Int) => i+1).map(_(42))
val res7: List[Int] = List(43)
Everything is an expression, but some things are simple expressions that don’t keep the underscore from bubbling up.