Eta-expansion between methods and functions with overloaded methods in Scala

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.

Leave a Comment