Value assigned to type member via method type parameter breaks type equivalence

96 Views Asked by At

Why does the following type equivalence hold

trait Foo { type T }
val fa = new Foo { type T = Int }

implicitly[fa.T =:= Int] // OK

but when type member T is assigned via method parameter A then type equivalence does not hold

def makeFoo[A]: Foo = new Foo { type T = A }
val fb = makeFoo[Int]

implicitly[fb.T =:= Int] // ERROR

Intuitively I would expect if T = A and A = Int then T = Int?

2

There are 2 best solutions below

0
On BEST ANSWER

This is because the return type of makeFoo is only Foo and not Foo { type T = A }. If you don't explicitly declare it or if you refine it, the error will go away.

def makeFoo[A] = new Foo { type T = A }
val fb = makeFoo[Int]

implicitly[fb.T =:= Int] // No more error

or

def makeFoo2[A]: Foo { type T = A } = new Foo { type T = A }
val fc: Foo { type T = Int } = makeFoo[Int]

implicitly[fc.T =:= Int] // No more error

Scastie:

<script src="https://scastie.scala-lang.org/knIfPcXqSQKXXCXJ2teHkg.js"></script>

0
On

I'll just add that possibility to specify not the precise return type but its supertype (an upper bound) is called whiteboxity.

In Scala 2 this can be achieved with whitebox macros

import scala.language.experimental.macros
import scala.reflect.macros.whitebox

def makeFoo[A]: Foo = macro impl[A]
def impl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
  import c.universe._
  q"new Foo { type T = ${weakTypeOf[A]} }"
}

val fb = makeFoo[Int]
implicitly[fb.T =:= Int] //compiles

In Dotty this can be achieved with keyword transparent (currently)

transparent inline def makeFoo[A]: Foo = new Foo { type T = A }
val fb = makeFoo[Int]
summon[fb.T =:= Int] //compiles

Former syntax is

inline def makeFoo[A] <: Foo = new Foo { type T = A }