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()) // ???
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With