Suppose I have a trait with a type parameter:
trait A[T]
I can use an existential type to write a method that will take a collection of A
s that all have the same T
:
def foo(as: Seq[A[X]] forSome { type X }) = true
Note that this is different from the following:
def otherFoo(as: Seq[A[X] forSome { type X }]) = true
Or the equivalent:
def otherFoo(as: Seq[A[_]]) = true
In these cases the scope of the existential is inside the Seq
, so the A
s can have different T
s. With my original foo
(with the existential scoping over the Seq
), the following is fine:
foo(Seq(new A[Int] {}, new A[Int] {}))
But make the type parameters different and it doesn't compile:
scala> foo(Seq(new A[Int] {}, new A[String] {}))
<console>:10: error: type mismatch;
found : Seq[A[_ >: java.lang.String with Int]]
required: Seq[A[X]] forSome { type X }
foo(Seq(new A[Int] {}, new A[String] {}))
^
This is all pretty straightforward.
Now suppose I have a similar trait with a type member instead of a type parameter:
trait B { type T }
I can write a method that will only take a B
with some specified T
:
scala> def bar[X](b: B { type T = X }) = true
bar: [X](b: B{type T = X})Boolean
scala> bar[Int](new B { type T = Int })
res5: Boolean = true
scala> bar[String](new B { type T = Int })
<console>:10: error: type mismatch;
found : java.lang.Object with B
required: B{type T = String}
bar[String](new B { type T = Int })
^
Again, this works exactly the way you'd expect it to.
When we try to write the equivalent of our foo
above, but for type members, things get weird.
scala> def baz(bs: Seq[B { type T = X }] forSome { type X }) = true
baz: (as: Seq[B{type T = X}] forSome { type X })Boolean
scala> baz(Seq(new B { type T = Int }, new B { type T = String }))
res7: Boolean = true
That the last line compiles makes no sense to me. I've told it that I want all the type members to be the same. My foo
shows that I can do this for type parameters, and bar
shows that I can constrain a type based on its type members. But I can't combine the two.
I've tried this on 2.9.2 and 2.10.0-M5.
This question is inspired by this one, where my first thought was, oh, just use an existential type (setting aside for a second the issue that it seems to be impossible to get an existential type to scope of the type of a repeated parameter, which would be handy here):
def accept(rs: Seq[RList[Int] { type S = X }] forSome { type X }) = true
But this doesn't actually work—you get the same weird result as in the simplified example above.
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.
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.
I've finally sort it out (at least I hope so). Let's do it the other way. We build our trait:
scala> trait B {type T}
defined trait B
We try and build a sequence of B
:
scala> Seq(new B {type T = Int}, new B {type T = String})
res0: Seq[B{type T >: String with Int}] = List($anon$1@592b12d, $anon$2@61ae0436)
Damn, it works! Ok, we don't have an equality for type T
but let's play with it:
scala> res0 : (Seq[B {type T = X}] forSome {type X >: String with Int})
res1: Seq[B{type T = X}] forSome { type X >: String with Int } = List($anon$1@592b12d, $anon$2@61ae0436)
It's closer. No wait, it's not closer, It's better than what you've proposed as a parameter of baz
, we do not only provide a raw type, we also have a upper bound! Thus, we can clearly pass it to baz
. This is why it doesn't work as you expected.
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