8

This following bugs me:

trait Foo[A]
class Bar[A](set: Set[Foo[A]] = Set.empty)

This yields

<console>:8: error: polymorphic expression cannot be instantiated to expected type;
 found   : [A]scala.collection.immutable.Set[A]
 required: Set[Foo[?]]
       class Bar[A](set: Set[Foo[A]] = Set.empty)
                                           ^

It is quite annoying that I have to repeat the type parameter in Set.empty. Why does the type inference fail with this default argument? The following works:

class Bar[A](set: Set[Foo[A]] = { Set.empty: Set[Foo[A]] })

Please note that this has nothing to do with Set in particular:

case class Hallo[A]()
class Bar[A](hallo: Hallo[A] = Hallo.apply)  // nope

Strangely not only this works:

class Bar[A](hallo: Hallo[A] = Hallo.apply[A])

...but also this:

class Bar[A](hallo: Hallo[A] = Hallo())      // ???
0__
  • 66,707
  • 21
  • 171
  • 266
  • 2
    Not an answer, but three things to note: you might want to name the type parameter something other than `A` to avoid confusion with the (different) `A` in the `found: [A]scala.collection.immutable.Set[A]` message; the important fact about both `Set` and your `Hallo` is that they're invariant (as opposed to say `List`); and your last line compiles put probably doesn't do what you want. – Travis Brown Oct 28 '12 at 18:19
  • 1
    While `class Bar[ A ]( hallo: Hallo[ A ] = Hallo.apply )` if you change it to use `Hallo.apply()` it works fine. You _should_ be able to leave the parens off, so it must be getting really confused here. It thinks you're passing the partially-applied function `Hallo.apply` instead of calling `apply` with no arguments. (The error message says it found type `[A]()Hallo[A]`.) – DaoWen Oct 28 '12 at 18:24

1 Answers1

6

You can specify the type directly on the empty method rather than having to add the extra set of parens/braces and the type annotation:

class Bar[A]( set: Set[Foo[A]] = Set.empty[Foo[A]] )

As for why the type inference fails, see these questions:

Update:

I apologize, my hasty answer was way off. The issue in the posts above isn't really related to this issue. @TravisBrown made a very good point in his comment above. This appears to work at first:

class Bar[A]( set: Set[A] = Set.empty )

But if you actually try to call the constructor it fails at use-site:

new Bar[Int]
//  <console>:9: error: type mismatch;
//   found   : scala.collection.immutable.Set[Nothing]
//   required: Set[Int]
//  Note: Nothing <: Int, but trait Set is invariant in type A.
//  You may wish to investigate a wildcard type such as `_ <: Int`. (SLS 3.2.10)
//  Error occurred in an application involving default arguments.
//                new Bar[Int]

This suggests that the compiler doesn't force the default parameter to be valid for all A, just for some A. They probably made that choice so you can do something like this:

scala> case class MyClass[T](set: Set[T] = Set(0))
defined class MyClass

scala> MyClass() // defaults to MyClass[Int]
res0: MyClass[Int] = MyClass(Set(0))

scala> MyClass(Set('x)) // but I can still use other types manually
res1: MyClass[Symbol] = MyClass(Set('x))

However, any sort of nesting with the parameterized type fails to type check at the declaration site in the constructor:

class Bar[A]( set: Set[Option[A]] = Set.empty )
// <console>:7: error: polymorphic expression cannot be instantiated to expected type;
//  found   : [A]scala.collection.immutable.Set[A]
//  required: Set[Option[?]]
//        class Bar[A]( set: Set[Option[A]] = Set.empty )

The inference doesn't fail if the type parameter is in a covariant position:

class Bar[ A ]( set: List[Foo[A]] = List.empty ) // OK

class Bar[ A ]( set: Map[Int,Foo[A]] = Map.empty ) // OK (unless you use it)

class Bar[ A ]( set: Map[Foo[A],Int] = Map.empty ) // BAD
// <console>:8: error: polymorphic expression cannot be instantiated to expected type;
//  found   : [A, B]scala.collection.immutable.Map[A,B]
//  required: Map[Foo[?],Int]
//            class Bar[ A ]( set: Map[Foo[A],Int] = Map.empty ) // BAD
//                                                       ^

These are working because the compiler selects Nothing as the covariant type by default. This works fine for List, but the second example above doesn't work if you actually try to call it.

The cause of most of this weirdness is probably the way that Scala handles default arguments. The compiler automatically adds an extra method to the companion object, and then wherever you leave off out an argument the compiler automatically adds a method call to the new method in the companion object to generate the missing argument instead. It looks like abstracting the default argument out into a method breaks some things in type inference that would work with a normal assignment.

I think most of these findings are pretty confusing. What I take away from this is that it's important to actually test your default parameters to be sure they don't break type correctness when you try to use them!

Community
  • 1
  • 1
DaoWen
  • 32,589
  • 6
  • 74
  • 101
  • Yes, I am aware of the type parameters for `empty`; I just wanted to show that the casting fully resolves the type, unlike the expected type for the default argument. This does not make sense to me. Also I don't see this related to questions regarding `toSet` to which you link. – 0__ Oct 28 '12 at 17:52
  • You're right, after I posted my answer I realized that this wasn't really the same case as the other two links I posted either. Let me do a bit more digging... However, since it works if you use a `List` instead of a `Set` in your example, I'm thinking it's related. – DaoWen Oct 28 '12 at 17:54
  • @0__ - I've made some pretty substantial edits to my answer, so you might want to read over it again. I'm still not sure if I've answered your initial question—I may have only added more questions. – DaoWen Oct 28 '12 at 22:58
  • You seem to be right with your guess "This suggests that the compiler doesn't force the default parameter to be valid for all A, just for some A." In fact, SLS §4.6 states that for "x: T = e", "e is type-checked with an expected type T′ obtained by replacing all occurences of the function’s type parameters in T by the undefined type (!!)." It follows an example similar to yours: `def compare[T](a: T = 0)(b: T = a) = (a == b)` which allows the inference of `Int`. Still I think it would be sensible (and not violate the SLS) if `Set.empty` was treated as `Set.empty[A]` and not `Set.empty[_]` – 0__ Oct 28 '12 at 23:20