9
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.

math4tots
  • 8,540
  • 14
  • 58
  • 95
  • 4
    Overloading and type inference interact in ways that are sometimes surprising. Without the overload, the type of the argument `Set(new B)` is inferred to be `Set[A]`. With the overload, the type of the argument is inferred independently (without reference to the possibly expected types), which results in `Set[B]`, which doesn't compile. – Travis Brown Oct 06 '16 at 18:09
  • 2
    @TravisBrown you should add this as an answer as it seems the correct one. – Denis Rosca Oct 06 '16 at 19:01
  • @DenisRosca Yea, I really like TravisBrown's concise and clear answer. I can't accept a comment as an answer :p – math4tots Oct 06 '16 at 19:11

2 Answers2

4

In the working case, the type param to Set.apply[T] is inferred to be A because Set[A] is the expected type of the function param.

Overloading resolution typechecks arguments without an expected type, so the compiler can no longer use Set[A] to guide inference of what set you intend.

That's an important take-away from the spec, though now it is a bit buried by more words about SAMs.

Otherwise, let Si... be the list of types obtained by typing each argument as follows. [Something about functions.] All other arguments are typed with an undefined expected type.

If it knows a Set[A] is expected, your set is typed that way, not as a Set[B].

You can observe typing decisions with -Ytyper-debug, which emits output that is occasionally not inscrutable.

Given

class A ; class B extends A

object X { def f(as: Set[A]) = ??? ; def f(s: String) = ??? }

object Main extends App {
  X.f(Set(new B))
}

Here, the value argument is typed as Set[B], and then it attempts and fails to find an implicit conversion to the param types of the overload.

It also looks for a conversion of the X object to a type with an f method that takes Set[B].

|    |-- X.f(Set(new B())) BYVALmode-EXPRmode (site: value <local Main> in Main)
|    |    |-- X.f BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |-- X EXPRmode-POLYmode-QUALmode (silent: value <local Main> in Main)
|    |    |    |    \-> X.type
|    |    |    \-> (s: String)Nothing <and> (as: Set[A])Nothing
|    |    |-- Set(new B()) BYVALmode-EXPRmode (silent: value <local Main> in Main)
|    |    |    |-- Set BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |-- scala.Predef.Set.apply BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |    [adapt] [A](elems: A*)CC[A] adapted to [A](elems: A*)CC[A]
|    |    |    |    |    \-> (elems: A*)scala.collection.immutable.Set[A]
|    |    |    |    [adapt] => scala.collection.immutable.Set.type adapted to [A](elems: A*)CC[A]
|    |    |    |    \-> (elems: A*)scala.collection.immutable.Set[A]
|    |    |    |-- new B() BYVALmode-EXPRmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |-- new B BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |    |-- new B EXPRmode-POLYmode-QUALmode (silent: value <local Main> in Main)
|    |    |    |    |    |    |-- B FUNmode-TYPEmode (silent: value <local Main> in Main)
|    |    |    |    |    |    |    \-> B
|    |    |    |    |    |    \-> B
|    |    |    |    |    \-> ()B
|    |    |    |    \-> B
|    |    |    solving for (A: ?A)
|    |    |    \-> scala.collection.immutable.Set[B]
|    |    [search #1] start `(s: String)Nothing <and> (as: Set[A])Nothing`, searching for adaptation to pt=scala.collection.immutable.Set[B] => String (silent: value <local Main> in Main) implicits disabled
|    |    15 implicits in companion scope
|    |    [search #2] start `(s: String)Nothing <and> (as: Set[A])Nothing`, searching for adaptation to pt=(=> scala.collection.immutable.Set[B]) => String (silent: value <local Main> in Main) implicits disabled
|    |    15 implicits in companion scope
|    |    [search #3] start `(s: String)Nothing <and> (as: Set[A])Nothing`, searching for adaptation to pt=scala.collection.immutable.Set[B] => Set[A] (silent: value <local Main> in Main) implicits disabled
|    |    15 implicits in companion scope
|    |    [search #4] start `(s: String)Nothing <and> (as: Set[A])Nothing`, searching for adaptation to pt=(=> scala.collection.immutable.Set[B]) => Set[A] (silent: value <local Main> in Main) implicits disabled
|    |    15 implicits in companion scope
|    |    second try: <error> and Set(new B())
|    |    |-- Set(new B()) EXPRmode (silent: value <local Main> in Main)
|    |    |    |-- Set BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |-- scala.Predef.Set.apply BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |    [adapt] [A](elems: A*)CC[A] adapted to [A](elems: A*)CC[A]
|    |    |    |    |    \-> (elems: A*)scala.collection.immutable.Set[A]
|    |    |    |    [adapt] => scala.collection.immutable.Set.type adapted to [A](elems: A*)CC[A]
|    |    |    |    \-> (elems: A*)scala.collection.immutable.Set[A]
|    |    |    solving for (A: ?A)
|    |    |    \-> scala.collection.immutable.Set[B]
|    |    [search #5] start `X.type`, searching for adaptation to pt=X.type => ?{def f(x$1: ? >: scala.collection.immutable.Set[B]): ?} (silent: value <local Main> in Main) implicits disabled
|    |    [search #6] start `X.type`, searching for adaptation to pt=(=> X.type) => ?{def f(x$1: ? >: scala.collection.immutable.Set[B]): ?} (silent: value <local Main> in Main) implicits disabled
badset.scala:7: error: overloaded method value f with alternatives:
  (s: String)Nothing <and>
  (as: Set[A])Nothing
 cannot be applied to (scala.collection.immutable.Set[B])
som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • It seems that you missed few things, Read this part of spec - ` [Spec 6.26.1 ](http://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#value-conversions)`. Overloading resolution is just one of the seven approaches used by Scala compiler when it encounters an expression `e` which has some value type `T` but is type-checked with some expected type `pt`. – sarveshseri Oct 06 '16 at 19:55
  • @SarveshKumarSingh I added the debug output. I hope that's clear for you. – som-snytt Oct 06 '16 at 21:52
  • Yup... and you see... implicits are used to determine `Compatibility`. I deduced my implicits theory after looking at debug output on various such occasions.Added some spec details to my answer. – sarveshseri Oct 06 '16 at 22:05
  • Anyway the exercise was useful in finding a minor bug with debug: https://github.com/scala/scala/pull/5444 – som-snytt Oct 07 '16 at 03:45
3

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.foos. 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.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 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
sarveshseri
  • 13,738
  • 28
  • 47
  • Type inference is used for resolving polymorphic functions. His `foo` isn't polymorphic. – Yuval Itzchakov Oct 06 '16 at 18:32
  • Type inference is used to decide the most suitable type to refer when an explicit type is not provided. It is not actually related or limited to polymorphic function. it is just that polymorphic functions depend on this capability not the vice-versa. For example in case of `val i = 5`, `i` is infered as `Int` where as if we write `val i = 5d` then `i` is infered as `Double` and there are no polymorphic function involved in either `val i = 5` or `val i = 5d`. – sarveshseri Oct 06 '16 at 18:37
  • [Not according to the specification](http://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#local-type-inference): *"Local type inference infers type arguments to be passed to **expressions of polymorphic type**."* – Yuval Itzchakov Oct 06 '16 at 18:39
  • Perhaps the type inference in this case is referring to the inference of the `Set[A]` type, since no type is specified, since the specification says "polymorphic type". – Yuval Itzchakov Oct 06 '16 at 18:43
  • As I said... `type-inference` is a fundamental capability of Scala which is used not only by polymorphic functions but also lots of other things like `higher-kinded-types`, `dependent-types` etc. Here by saying fundamental, I mean that it one of the building blocks of Scala at its very foundations. – sarveshseri Oct 06 '16 at 18:44
  • I understand that it is a fundamental element in any polymorphic type system. I'm wondering if this is actually applicable here. – Yuval Itzchakov Oct 06 '16 at 18:45
  • Yes... `type-inference` just says that if the type is not provided explicitly then figure it out using the information that you have. Any expression which does not clarify its type explicitly is potentially polymorphic (Since everything in Scala can also be seen as either `Any` or even `Nothing` when type-inference fails). – sarveshseri Oct 06 '16 at 18:46
  • You wrote, "or `implicitly convert` it to a `String`." But it won't implicitly convert it to a String. Comment out the 1st `foo()` and it won't compile. `(new B()).toString` is an explicit conversion. – jwvh Oct 06 '16 at 18:53
  • @jwvh That is where things are a bit confusing for me too. My opinion is that the reason of scala compiler getting confused is because it has the capability to `implicitly-convert` to desired types if any suitable implicit is in scope and thus it deems that it has multiple choices of equal preference. For a better and more accurate answer we will have to consult someone who has in-depth understanding. May be we can discuss this on Scala's gitter. – sarveshseri Oct 06 '16 at 19:20
  • @son-snytt Yup... I too have a very low confidence on the accuracy of the part about `implicit conversion` but my limited understanding says that it is the rough reason. – sarveshseri Oct 06 '16 at 19:22
  • @som-snytt the thing is Scala compiler will always get confused in case of overloaded methods with same number of parameters unless the types are explicitly specified. But if we consider the ability of Scala's type-inference to successfully infer the types in even in complicated maze of some very convulted higher kinded types, it should be very easy for it to succeed in such simple cases, but it can not. Just why is that ? – sarveshseri Oct 06 '16 at 19:29
  • @som-snytt Actually after spending some more time to read the spec, now I am sure that my reasoning is indeed correct. [Spec 6.26.1 ](http://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#value-conversions) talks about potential value conversions and says `The following seven implicit conversions can be applied to an expression ee which has some value type TT and which is type-checked with some expected type ptpt.` ` – sarveshseri Oct 06 '16 at 19:47
  • @som-snytt And I also realise that my current beginner friendly explanation of why this case is causing two of these approaches to interfere with each other is misleading. I will try to convert it to be closer to spec. – sarveshseri Oct 06 '16 at 20:14