My previous attempt to explain how underscore works is here.
The underscore expands at the enclosing Expr
which is not just a bare underscore.
f(_)
is
x => f(x)
as one might wish and not
f(x => x)
Here, “function argument” is an Expr
.
f(_ + 1)
is
f(x => x + 1)
because the argument to f
is not just an underscore, but an expression with underscore as a subexpression.
The nice feature of infix is that the operands are infix expressions and don’t “bind” the underscore.
Here is an example, although you wouldn’t normally write it this way:
List(42).flatMap(List(_ + 1)) // doesn't work, see previous
but
List(42).flatMap(List apply _ + 1) // looks weird but works
In the working example, the underscore is not “bound” at the infix expressions, but at the enclosing expression which is the argument to flatMap
. Normal precedence applies, so it means what the broken form looks like it should mean:
List(42).flatMap(x => List(x + 1))
Here is the function composition in Scala 3, where it’s easier to write f
instead of f _
:
scala> def g(s: String, left: Char, right: Char) = s"g$left$s$right"
def g(s: String, left: Char, right: Char): String
scala> def f(s:String) = s"f($s)"
def f(s: String): String
scala> f andThen g(_, '[', ']')
-- [E081] Type Error: --------------------------------------------------------------------------------------------------
1 |f andThen g(_, '[', ']')
| ^
| Missing parameter type
|
| I could not infer the type of the parameter _$1 of expanded function:
| _$1 => f andThen g(_$1, '[', ']').
1 error found
scala> g(_, '[', ']')
val res0: String => String = Lambda$1450/0x00000008010c0410@6ceb11f9
scala> f andThen res0
val res1: String => String = scala.Function1$$Lambda$1469/0x00000008010dc520@31b650e9
scala> res1("X")
val res2: String = g[f(X)]
scala> f.andThen(g(_, '[', ']'))
val res3: String => String = scala.Function1$$Lambda$1469/0x00000008010dc520@6c06b1bc
This is the converse of the previous example, where the broken form doesn’t work because the infix expression which is the operand to andThen
does not bind the underscore. Turning it into a regular argument in regular function call syntax makes it an Expr
that does bind the underscore.
Both Scala 2 and 3 show the erroneous expansion of the broken form.
As a further nuance, x op y
has infix expressions, but x op (y)
turns the y
into an Expr
production, just the same as x.op(y)
. (x, y, z)
is Expr
s in parens, where it doesn’t matter whether you see them as args to a function or a tuple.