Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checking the equality of types involving existentials in Scala

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?

Context

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.

like image 780
Alex Archambault Avatar asked Oct 08 '14 00:10

Alex Archambault


1 Answers

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!

like image 197
dk14 Avatar answered Oct 14 '22 04:10

dk14