Automatic Eta Expansion - More Details
Motivation
Scala maintains a convenient distinction between methods and functions.
Methods are part of the definition of a class that can be invoked in objects while functions are complete objects themselves, making them first-class entities. For example, they can be assigned to variables.
These two mechanisms are bridged in Scala by a mechanism called eta-expansion (also called eta-abstraction), which converts a reference to a method into a function. Intuitively, a method m
can be passed around by turning it into an object: the function x => m(x)
.
In this snippet which assigns a method to a val
, the compiler will perform automatic eta-expansion, as shown in the comment:
def m(x: Int, y: String) = ???
val f = m // becomes: val f = (x: Int, y: String) => m(x, y)
In Scala 2, a method reference m
was converted to a function value only if the expected type was a function type, which means the conversion in the example above would not have been triggered, because val f
does not have a type ascription. To still get eta-expansion, a shortcut m _
would force the conversion.
For methods with one or more parameters like in the example above, this restriction has now been dropped. The syntax m _
is no longer needed and will be deprecated in the future.
Automatic eta-expansion and partial application
In the following example m
can be partially applied to the first two parameters.
Assignining m
to f1
will automatically eta-expand.
def m(x: Boolean, y: String)(z: Int): List[Int]
val f1 = m
val f2 = m(true, "abc")
This creates two function values:
f1: (Boolean, String) => Int => List[Int]
f2: Int => List[Int]
Automatic eta-expansion and implicit parameter lists
Methods with implicit parameter lists will always get applied to implicit arguments.
def foo(x: Int)(implicit p: Double): Float = ???
implicit val bla: Double = 1.0
val bar = foo // val bar: Int => Float = ...
Automatic Eta-Expansion and query types
A method with context parameters can be expanded to a value of a context type by writing the expected context type explicitly.
def foo(x: Int)(using p: Double): Float = ???
val bar: Double ?=> Float = foo(3)
Rules
- If
m
has an argument list with one or more parameters, we always eta-expand - If
m
is has an empty argument list (i.e. has type()R
):- If the expected type is of the form
() => T
, we eta expand. - If m is defined by Java, or overrides a Java defined method, we insert
()
. - Otherwise we issue an error of the form:
- If the expected type is of the form
Thus, an unapplied method with an empty argument list is only converted to a function when a function type is expected. It is considered best practice to either explicitly apply the method to ()
, or convert it to a function with () => m()
.
The method value syntax m _
is deprecated.
Reference
For more info, see PR #2701.