I'm writing a function of the form
def test[A,B](a: A, b: B)(implicit eq: A =:= B): Unit = ...
where I require an evidence that types A
and B
are the same.
I would expect calls of the form test(a,a)
to compile for any a
, but it seems not to be the case when a
's type involves existentials, like in
case class Foo[T](c: T)
val l = List(Foo(1), Foo(1.0F), Foo(0.0)) // Inferred type: List[Foo[_ >: Double with Float with Int <: AnyVal]]
test(l.head, l.head) // Does not compile, error like: Cannot prove that Foo[_7] =:= Foo[_7].
So my question is: am I mis-using =:=
? Or could it be a bug? Or a fundamental limitation of existentials? Or a limitation of their implementation?
I'm testing the return type of a function f
involving dependent types.
I expect it to return the same type for a
and b
in val a = f(...); val b = f(...)
, thus I call test(a, b)
. If a
and b
's types involve existentials, even test(a,a)
and test(b,b)
don't compile, as described above.
Implicit =:= works fine for existential types
scala> implicitly[Foo[_ <: Int] =:= Foo[_ <: Int]]
res0: =:=[Foo[_ <: Int],Foo[_ <: Int]] = <function1>
scala> implicitly[Foo[_ <: Int] =:= Foo[_]]
<console>:10: error: Cannot prove that Foo[_ <: Int] =:= Foo[_].
implicitly[Foo[_ <: Int] =:= Foo[_]]
^
The problem is scala loses existential type when resolving implicit for function call - A and B are not inferred here (that's why you see _7 denotation and no implicits are found).
def test[A,B](a: A, b: B)(implicit eq: A =:= B)
It can be solved with type alias:
scala> case class Foo[A](a: A) {type X = A}
defined class Foo
scala> val l = List(Foo(1), Foo(1.0F), Foo(0.0)) // Inferred type: List[Foo[_ >: Double with Float with Int <: AnyVal]]
l: List[Foo[_ >: Double with Float with Int <: AnyVal]] = List(Foo(1), Foo(1.0), Foo(0.0))
scala> val k = l.head
k: Foo[_ >: Double with Float with Int <: AnyVal] = Foo(1)
scala> implicitly[k.X =:= k.X]
res15: =:=[_ >: Double with Float with Int <: AnyVal, _ >: Double with Float with Int <: AnyVal] = <function1>
scala> implicitly[k.X =:= _ >: Double with Float with Int <: AnyVal]
res16: =:=[_ >: Double with Float with Int <: AnyVal, _ >: Double with Float with Int <: AnyVal] = <function1>
But keep in mind that even types with similar signature, but from different Foo's will be different (because it's path-dependent types) - existential types are so existential!
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