Methods' composition

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 Exprs in parens, where it doesn’t matter whether you see them as args to a function or a tuple.

1 Like