Search code examples
scalatypesexistential-type

Why does scala require existential types to restrict a generic bound?


With the following class hierarchy:

trait Provider[A] {
  def get(): Seq[A]
}

abstract class ProviderImpl[A] extends Provider[A] {
  final override def get(): Seq[A] = Seq()
}

trait HasX {
  def getX: Int
}

trait RefinedProvider[A <: HasX] extends Provider[A]

class TypedProviderImpl extends ProviderImpl[HasX] with RefinedProvider[HasX]

I want to be able to do this:

val provider: RefinedProvider[_] = new TypedProviderImpl()
provider.get() map (_.getX)

But it doesn't work, because the return type of provider.get() is Seq[Any] which seems wrong to me because it's a RefinedProvider, so get() should return a Seq[_ <: HasX].

Question: I can fix the problem with an existential type, but why can't the compiler enforce this for me?

val provider: RefinedProvider[T] forSome { type T <: HasX } = ...

Solution

  • Ticket SI-2385 suggests this is simply part of the spec to interpret A[_] as A[T] forSome { type T >: Nothing <: Any } and not infer tighter bounds if possible. One could wonder whether the spec shouldn't be updated then.

    Ticket SI-6169 seems to suggest that some things would stop working if tighter bounds were inferred. I'm not sure how and why though.

    A small compromise is that you can shorten

    val provider: RefinedProvider[T] forSome { type T <: HasX }
    

    to

    val provider: RefinedProvider[_ <: HasX]