I would like to understand why the eta-expansion (§6.26.5) does not work for overloaded methods. For example, if I have following two methods:
def d1(a: Int, b: Int) {}
def r[A, B](delegate: (A, B) ⇒ Unit) {}
I can do this:
r(d1)
But, when overloading r
it will no longer work:
def r[A, B](delegate: (A, B) ⇒ Unit) {}
def r[A, B, C](delegate: (A, B, C) ⇒ Unit) {}
r(d1) // no longer compiles
and I have to explicitly convert method into partially applied function:
r(d1 _)
Is there any way to accomplish following with the explicit conversion?
def r[A, B](delegate: (A, B) ⇒ Unit) {}
def r[A, B, C](delegate: (A, B, C) ⇒ Unit) {}
def d1(a: Int, b: Int) {}
def d2(a: Int, b: Int, c: Int) {}
r(d1) // only compiles with r(d1 _)
r(d2) // only compiles with r(d2 _)
There is somewhat similar question, but it is not fully explained.
Implicit is the correct term, and the section is 6.26.2 in the spec, and this must be a duplicate question (or so one would think; this is stable behavior).
The linked question also answers that the expected type must be a function.
I'll go out on a limb and say that when overloaded, applicability is undermined because there is no expected type (6.26.3, infamously). When not overloaded, 6.26.2 applies (eta expansion) because the type of the parameter determines the expected type. When overloaded, the arg is specifically typed with no expected type, hence 6.26.2 doesn't apply; therefore neither overloaded variant of d
is deemed to be applicable.
From 6.26.3 Overloading Resolution
Otherwise, let S 1 , . . . , S m be the vector of types obtained by typing each argument with an undefined expected type.
Here are the "implicit conversions" (so-called) available when you name a method without args, as in r(d1)
. The paragraph on eta expansion applies here.
6.26.2 Method Conversions
The following four implicit conversions can be applied to methods which are not applied to some argument list.
Evaluation. A parameterless method m of type => T is always converted to type T by evaluating the expression to which m is bound.
Implicit Application. If the method takes only implicit parameters, implicit argu- ments are passed following the rules of §7.2.
Eta Expansion. Otherwise, if the method is not a constructor, and the expected type pt is a function type (Ts ) ⇒ T , eta-expansion (§6.26.5) is performed on the expression e.
Empty Application. Otherwise, if e has method type ()T , it is implicitly applied to the empty argument list, yielding e()
More post-green-check explanation...
The following example demonstrates preferring application to eta-expansion in the presence of overloading. When eta-expansion doesn't apply, "empty application" is the final implicit to try in 6.26.2. In other words, when overloading (which is confusing and evil enough on the face of it), it is natural to take f
as f()
by the uniform access principle, but it is unnatural or weird to take f
as f _
unless you're quite sure a function type is expected.
scala> object Bar {
| def r(f: () => Int) = 1
| def r(i: Int) = 2
| }
defined module Bar
scala> def f() = 4
f: ()Int
scala> Bar.r(f)
res4: Int = 2
scala> Bar.r(f _)
res5: Int = 1
Candidates for overloading resolution are pre-screened by "shape". The shape test encapsulates the intuition that eta-expansion is never used because args are typed without an expected type. This example shows that eta-expansion is not used even when it is "the only way for the expression to type check."
scala> object Bar {
| def bar(f: () => Int) = 1
| def bar(is: Array[Int]) = 2
| }
defined object Bar
scala> def m() = 7
m: ()Int
scala> m _
res0: () => Int = <function0>
scala> Bar.bar(m)
<console>:10: error: overloaded method value bar with alternatives:
(is: Array[Int])Int <and>
(f: () => Int)Int
cannot be applied to (Int)
Bar.bar(m)
^
Anyone reading this far will be curious about a related issue with these two conversions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With