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)
^
Existential types, or 'existentials' for short, are a way of 'squashing' a group of types into one, single type. Existentials are part of GHC's type system extensions.
Type declaration is a Scala feature that enables us to declare our own types. In this short tutorial, we'll learn how to do type declaration in Scala using the type keyword. First, we'll learn to use it as a type alias. Then, we'll learn to declare an abstract type member and implement it.
Existentials in Swift allow defining a dynamic value conforming to a specific protocol. Using primary associated types, we can constrain existentials to certain boundaries. The Swift team introduced the any keyword to let developers explicitly opt-in to a performance impact that might otherwise not be visible.
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.
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