math4tots math4tots - 2 months ago 18
Scala Question

Why does scala fail to compile when method is overloaded in a seemingly unrelated way?

class A {}
class B extends A {}

object Sample {
def foo(a: Set[A]) {
println("Hi Set[A]")
}
// def foo(a: String) {
// println("Hi A")
// }
}

Sample.foo(Set(new B()))


The above code runs happily with
scala
. However, when I uncomment
foo(a: String)
, the code fails to compile:

test.scala:13: error: overloaded method value foo with alternatives:
(a: String)Unit <and>
(a: Set[this.A])Unit
cannot be applied to (scala.collection.immutable.Set[this.B])
Sample.foo(Set(new B()))
^
one error found


foo(a: String)
seems like it should have nothing to do with trying to call
foo
with a
Set[B]
. What is going on?

EDIT:

The thing that confuses me isn't just why the uncommented version doesn't compile, but also why it does compile, when
foo(a: String)
is commented out. What am I changing by adding the method
foo(a: String)
?

Set
being invariant doesn't explain why it compiles successfully when
foo(a: String)
is commented out.

Answer

Actually... the true answer of this question was hidden away in @pamu's answer. The answer to this is bit non-trivial and will take a lot of explaining.

Let us first consider op's first case which compiles,

class A {}
class B extends A {}

object Sample {
  def foo(a: Set[A]) {
    println("Hi Set[A]")
  }
}

Sample.foo(Set(new B()))

But why did it compile ? Well... the answer lies in the fact that Scala-compiler is a very intelligent creature and has the capability of type-inference. This means that if the type is not explicitly provided Scala tries to guess the type which user probably wanted by looking at the available information and the treats it as most suitable (closest fit) type.

Now, in Sample.foo(Set(new B())), Scala finds that the foo takes a Set[A] as a parameter. It looks at provided parameter Set(new B()) which looks more like a Set[B]... but how can the Scala-compiler's master "the programmer" can make a mistake. So it checks if it can actually infer it as a Set[A]. And it succeeds. Scala compiler is happy and proud that it is intelligent enough to understand it's master's profound intentions.

To explain it even more clearly... let me show what happens when you tell Scala types explicitly and Scala does not need to use any of its inference intelligence.

// tell scala that it is a set of A
// and we all know that any set of A can contain B
scala> val setA: Set[A] = Set(new B())
setA: scala.collection.immutable.Set[A] = Set(B@17ae2a19)

// Scala is happy with a Set[A]
scala> Sample.foo(setA)
// Hi Set[A]

// tell scala that it is a set of B
// and we all know that any set of B can contain B
scala> val setB: Set[B] = Set(new B())
// setB: scala.collection.immutable.Set[B] = Set(B@17ae2a19)

// But Scala knows that Sample.foo needs a Set[A] and not Set[B]
scala> Sample.foo(setB)
// <console>:20: error: type mismatch;
//  found   : scala.collection.immutable.Set[B]
//  required: Set[A]
// Note: B <: A, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: A`. (SLS 3.2.10)
//        Sample.foo(setB)
              ^

Now that we know why first case worked for OP. Lets move on to the second case.

class A {}
class B extends A {}

object Sample {
  def foo(a: Set[A]) {
    println("Hi Set[A]")
  }
  def foo(a: String) {
    println("Hi A")
  }
}

Sample.foo(Set(new B()))

Now... all of a sudden Sample.foo(Set(new B())) does not compile.

The reason is again hidden in the "intelligence" of Scala compiler. You all should be aware that other than type-inference Scala has another magical thing call implicit conversions with the help of those magical implicit type-class. One such conversion which everyone seems to like very much is T => String implicit conversion for any type.

And now you should be able to see the problem. Scala compiler now see's two Sample.foos. First wants a Set[A] and other wants a String. But what Scala has looks more like a Set[B]. Now it can either infer it as a Set[A] or implicitly convert it to a String.

Both of these choices look fairly reasonable to Scala and now this "intelligent" being is confused about what its noble master "the programmer" wanted. It dares not to make any mistake in its master's affairs and thus decides to tell the master about its confusion and ask for his wishes.

Now... how do we programmers help with its confusion... well we simply provide more information.

for example,

scala> Sample.foo(Set[A](new B()))
// Hi Set[A]

// Or for string

scala> Sample.foo(Set[A](new B()).toString)
// Hi A

// Or,

scala> val setA: Set[A] = Set(new B())
// setA: scala.collection.immutable.Set[A] = Set(B@17ae2a19)

scala> Sample.foo(setA)
// Hi Set[A]

// Or for string

scala> Sample.foo(setA.toString)
// Hi A