Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

existential types declarations in Scala [duplicate]

Little confusing about existential types.

This works for me:

def valueOf(c: Class[_], name: String) {
  type C = Class[T] forSome {type T <: Enum[T]}
  Enum.valueOf(c.asInstanceOf[C], name)
} 

but this does not:

def valueOf(c: Class[_], name: String) {
  type T = T forSome {type T <: Enum[T]}
  Enum.valueOf(c.asInstanceOf[Class[T]], name)
}

In my mind both expressions are equivalent to:

Enum.valueOf(z.asInstanceOf[Class[T] forSome {type T <: Enum[T]}], name)

But Scala says that it's in my mind only:

inferred type arguments [T] do not conform to method valueOf's type parameter bounds [T <: Enum[T]]
         Enum.valueOf(c.asInstanceOf[Class[T]], name)
              ^
like image 896
dk14 Avatar asked Aug 23 '13 06:08

dk14


1 Answers

Consider the difference between the following two expressions:

Enum.valueOf(x.asInstanceOf[Class[X] forSome { type X <: Enum[X] }], name)

And:

Enum.valueOf(x.asInstanceOf[Class[X forSome { type X <: Enum[X] }]], name)

Now think about how the type parameter T of valueOf will be inferred in each of these cases. In the first case, we've got an X that we know is a subtype of Enum[X], and we're all set. In the second case, on the other hand, T would have to be X forSome { type X <: Enum[X] }, and crucially this type is not a subtype of Enum[X forSome { type X <: Enum[X] }], so we haven't satisfied the constraint on T.

The problem is that your second example is equivalent to the latter.


As a footnote, this would work just fine if Enum were covariant in its type parameter. Take the following simplified example:

trait Foo[A]
trait Bar[A]

def foo[A <: Bar[A]](f: Foo[A]) = f

def x: Foo[X] forSome { type X <: Bar[X] } = ???
def y: Foo[Y forSome { type Y <: Bar[Y] }] = ???

Now foo(x) will compile, but foo(y) won't, just as in your code. But change Bar a bit:

trait Foo[A]
trait Bar[+A]

def foo[A <: Bar[A]](f: Foo[A]) = f

def x: Foo[X] forSome { type X <: Bar[X] } = ???
def y: Foo[Y forSome { type Y <: Bar[Y] }] = ???

Now they'll both compile. I'd guess that this has something to do with the reason that we have such strong intuitions about your two examples being equivalent.


As another footnote (in response to gzmo's comment below), consider the following:

scala> trait Foo[A <: Foo[A]]
defined trait Foo

scala> class MyFoo extends Foo[MyFoo]
defined class MyFoo

scala> val myFoo = new MyFoo
myFoo: MyFoo = MyFoo@3ee536d

scala> myFoo: (X forSome { type X <: Foo[X] })
res0: X forSome { type X <: Foo[X] } = MyFoo@3ee536d

scala> myFoo: Foo[MyFoo]
res1: Foo[MyFoo] = MyFoo@3ee536d

Let's suppose that X forSome { type X <: Foo[X] } were a subtype of Foo[X forSome { type X <: Foo[X] }] (ignoring for a moment the fact that the latter isn't even a valid type). Then we'd be able to write the following:

myFoo: Foo[X forSome { type X <: Foo[X] }]

But Foo is invariant, so if we have some thing that's an instance of both Foo[A] and Foo[B], then it must be the case that A =:= B. But it's definitely not the case that MyFoo =:= (X forSome { type X <: Foo[X] }). Not sure all of that is any less confusing, but it's how I convinced myself that the compiler knows what it's doing here.

like image 157
Travis Brown Avatar answered Sep 28 '22 11:09

Travis Brown