Simon K
Simon K

Reputation: 135

Can an existentially quantified type variable be forced to have only a single type?

Consider the following code

trait Foo[T] {
  def one: Foo[_ >: T]
  def two: T
  def three(x: T)
}

def test[T](f: Foo[T]) = {
  val b = f.one
  b.three(b.two)
}

The method test fails to type check. It says:

 found   : (some other)_$1(in value b)
 required: _$1(in value b)
     val x = b.three(b.two)

If I am interpreting this correctly, the compiler thinks that b in method test has a type that looks like this (not legal syntax, but hopefully clearer):

trait Foo {
   def two: X where ∃ X >: T
   def three(x: X where ∃ X >: T)
}

What I was hoping for was that it would have a type like this:

∃ X >: T such that trait Foo {
   def two: X
   def three(x: X)
}

The intent being that while the precise type X is not known, the compiler knows that its the same unknown type being returned by "two" and expected by "three". This seems different from what happens with normal univerally quantified generics. The following compiles, but exposes the type parameter X which I want to hide as it will vary between instances of Foo:

trait Foo[T] {
  def one[X >: T]: Foo[X]
  def two: T
  def three(x: T)
}

def test[T, X >: T](f: Foo[T]) = {
  val b = f.one[X]
  b.three(b.two)
}

Is there a way to get the same behaviour for existentially quantified generics we get when they're univerally quanified?

Upvotes: 4

Views: 139

Answers (2)

ggovan
ggovan

Reputation: 1927

The problem is the compiler thinks that

b.two: _>:T
b.three(_>:T)

i.e. two is a supertype of T and three requires a supertype of T. But a supertype of T is not necessarily assignment compatible with another supertype of T, as in this example:

A >: B >: C
def get:A
def put(B)
put(get) // type mismatch

So if all the information we have is that they are supertypes of T then we cannot do this safely. We have to explicitly tell the compiler that they are the same supertype of T.

trait Foo[T] {
  type U <: T
  def one: Foo[U]
  def two: T
  def three(x: T)
}

Then just set U when you implement the trait:

val x = new Foo[Dog]{
  type U = Mammal
  ...

I would prefer this approach over the existential types due to the cleaner syntax and the fact that this is core Scala and does not need the feature to be imported.

Upvotes: 2

stew
stew

Reputation: 11366

def one: Foo[_ >: T] is equivalent to

def one: Foo[U >: T] forSome {type U >: T}

this one instead works

def one: Foo[U forSome {type U >: T}]

I do not however understand why this would make a difference. It seems like it should not to me. (shrug)

Upvotes: 2

Related Questions