scala

Why is reference to overloaded definition ambiguous when types are known?


I have a function like so:

def ifSome[B, _](pairs:(Option[B], B => _)*) {
  for((paramOption, setFunc) <- pairs)
    for(someParam <- paramOption) setFunc(someParam)
}

and overloaded functions like these:

class Foo{ 
  var b=""
  def setB(b:String){this.b = b}
  def setB(b:Int){this.b = b.toString}
}

val f = new Foo

then the following line produces an error:

ifSome(Option("hi") -> f.setB _)

<console>:11: error: ambiguous reference to overloaded definition,
both method setB in class Foo of type (b: Int)Unit
and  method setB in class Foo of type (b: String)Unit
match expected type ?
                 ifSome(Option("hi") -> f.setB _)

But the compiler knows that we're looking for a Function1[java.lang.String, _], so why should the presence of a Function1[Int, _] present any confusion? Am I missing something or is this a compiler bug (or perhaps it should be a feature request)?

I was able to workaround this by using a type annotation like so

ifSome(Option("hi") -> (f.setB _:String=>Unit))

but I'd like to understand why this is necessary.


Solution

  • You'll want to try $ scalac -Ydebug -Yinfer-debug x.scala but first you'll want to minimize.

    In this case, you'll see how in the curried version, B is solved in the first param list:

    [infer method] solving for B in (bs: B*)(bfs: Function1[B, _]*)Nothing 
    based on (String)(bfs: Function1[B, _]*)Nothing (solved: B=String)
    

    For the uncurried version, you'll see some strangeness around

    [infer view] <empty> with pt=String => Int
    

    as it tries to disambiguate the overload, which may lead you to the weird solution below.

    The dummy implicit serves the sole purpose of resolving the overload so that inference can get on with it. The implicit itself is unused and remains unimplemented???

    That's a pretty weird solution, but you know that overloading is evil, right? And you've got to fight evil with whatever tools are at your disposal.

    Also see that your type annotation workaround is more laborious than just specifying the type param in the normal way.

    object Test extends App {
      def f[B](pairs: (B, B => _)*) = ???
      def f2[B](bs: B*)(bfs: (B => _)*) = ???
    
      def g(b: String) = ???
      def g(b: Int) = ???
    
      // explicitly
      f[String](Pair("hi", g _))
    
      // solves for B in first ps
      f2("hi")(g _)
    
      // using Pair instead of arrow means less debug output
      //f(Pair("hi", g _))
    
      locally {
        // unused, but selects g(String) and solves B=String
        import language.implicitConversions
        implicit def cnv1(v: String): Int = ???
        f(Pair("hi", g _))
      }
    
      // a more heavy-handed way to fix the type
      class P[A](a: A, fnc: A => _)
      class PS(a: String, fnc: String => _) extends P[String](a, fnc)
      def p[A](ps: P[A]*) = ???
      p(new PS("hi", g _))
    }