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.
The Scala specification section 6.26.1 refers to this as Type Instantiation
.
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. Scala compiler now see's two Sample.foo
s. First wants a Set[A]
and other wants a String
. How should Scala go around deciding which one the programmer intended. Looking at what is knows, Scala finds something that looks more like a Set[B]
.
Now as we discussed about Type Instantiation and inference, once scala knows what type to expect it can try to infer that type. But here Scala can not decide on what type to expect as it sees multiple choices. So before moving to type inference, it should deal with the problem of overloaded choice only then can it set its expectations for inference.
This is discussed in Overload Resolution
( Section 6.26.3) of Scala specification. The specification may look a little opaque so lets discuss how it tries to differentiate.
overload resolution
is actually composed of two problems,
Problem 1 :: Just considering the arguments provided, out of all alternatives which one's are more specifically applicable
. In other words we look at Applicability
of arguments on the alternatives available. Applicability
is discussed in Section 6.6. Applicability
first considers the shape of arguments provided and is heavily dependant on Compatibility
and Conformance
of each type parameter for further analysis.
Problem 2 :: Now, Considering the type of reference
to the method call, we try to decide which among the above selected alternatives are Compatible
to it.
Now, we come to realise the importance of Compatibility
which is discussed in detail in section 3.5.4. To give a brief idea, Compatibility
of two given types (which are not functions) depends on the implicit views
(implicit conversions
between two types)
If you go through the rules of overload resolution... you will see that Scala compiler will fail to resolve the multiple choices problem for call Sample.foo(Set(new B()))
. And thus no inference could be done and that argument which looks most like a Set[B]
is still treated as Set[B]
.
To put it in very in-accurate
(is just for easier visualization of the actual problem explained above and is not supposed to be treated as accurate in any way) but simple explanation -> 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
. Scala compiler now see's two Sample.foo
s. First wants a Set[A]
and other wants a String
. But what Scala has looks more like a Set[B]
. Now it can either try to infer
it as a Set[A]
or try to 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