Overcoming type erasure in Scala 3

Sometimes i get bitten by type erasure without an obvious (or elegant) way out. Very often this boils down to not being able to separate X[A] from X[B] in one way or an other. In Scala 2 you could call TypeTag to the rescue, but Scala 3 only provides ClassTag which is not powerful enough. For example:

import scala.reflect.ClassTag

class A
class B

class X[T <: A|B] :
  def sideEffect(t: T): Unit = println(t.toString) 

val a = new A
val b = new B 

def test[T <: A|B : ClassTag](x: X[T]): Unit = x match 
  case xc: X[A] => xc.sideEffect(a)
  case xc: X[B] => xc.sideEffect(b) 
  case _        => println("not found")

val xa = new X[A]()
val xb = new X[B]()

test(xa)
test(xb)

still results (scastie) in the warning:

the type test for X[A] cannot be checked at runtime

at the matching lines.

One solution (scastie) could be to add a dummy: T field to X[T] and test on that. Works, but no so elegant, and often not applicable.

Are there other ways in Scala 3 to solve this? I found the remark from Prof. Odersky about quoted.Type in a conversation about reflection, but have no idea how to use that. The online doc about this subject does not seem updated.

From what I understand its seems like you can use quotes (macros). As per the previous link, you can match on a specific type via quotes or use Type.of directly. Don’t forget to add this using explicitly or via a type parameter context.

I have not checked, but you can look at some examples to see if one can provides some “inspiration”.

EDIT:

BTW: I think an alternative way, that still uses additional parameters, is to add a 2nd using parameter and request evidence of the type via =:= . At the call site you need not add this implicit parameter.

HTHs

1 Like

you can use Izumi reflect which is “good enough” representation of a type tag for the case you present here

2 Likes

Just for the kicks, a very verbose but reflection-free/type-only take (perhaps a variant of the alternative approach @hmf suggested?).

sealed trait Disp[T <: A | B]:
  def disp(xc: X[T]): Unit

object Disp:

  class SubDisp[T <: A | B](t: T) extends Disp[T]:
    def disp(xc: X[T]): Unit = xc.sideEffect(t)

  given Disp[A] = new SubDisp(a)
  given Disp[B] = new SubDisp(b)
  given Disp[A | B] with
    def disp(xc: X[A | B]): Unit = println("not found")

def test[T <: A | B : Disp](x: X[T]): Unit = summon[Disp[T]].disp(x)
1 Like

Thanks @hmf , @bishabosha and @sangamon for the tips. Not exactly what i had hoped for, (namely something syntactically close to the original intent) but i guess that’s the price to pay for benefits of type erasure. Also, i am a bit reluctant to pull in a framework just for one or two cases.