1

Assume, we have an abstract class with an abstract type field:

abstract class A {type T}

Now let's assume, we have a method, which returns objects of type A, but the type field T may differ. How can we distinguish between these objects ?

We can try a pattern match:

object Test {
  def tryToDistinguish(a: A) = 
    a match {
      case b: A {type T = String} => println("String type")
      case b: A {type T = Int} => println("Int type")
      case b: A => println("Other type")
  }
}

But the compiler will complain:

$>scalac -unchecked Test.scala
Test.scala:8: warning: refinement example.test.A{type T = String} in type patter
n example.test.A{type T = String} is unchecked since it is eliminated by erasure

      case b: A {type T = String} => println("String type")
          ^
Test.scala:9: warning: refinement example.test.A{type T = Int} in type pattern e
xample.test.A{type T = Int} is unchecked since it is eliminated by erasure
          case b: A {type T = Int} => println("Int type")
              ^
two warnings found

It seems the type of the type field will be removed by erasure (side question: Because type fields are translated to parameter types in Java ?)

Therefore, this will not work:

scala> Test.tryToDistinguish(new A {type T = Int})
String type

Alternative : We can alternativley create an enumeration and put an additional field in the class A in order to distinguish the objects. But this smells, because it would mean, that we re-implement the type system.

Question : Is there a way to differ between the types of the objects with the help of the type field ? And if not, what will be a good workaround ?

John Threepwood
  • 15,593
  • 27
  • 93
  • 149

3 Answers3

2

You can use the <%< which tells you in compile-time if two types are compatible.

scala> class A { type T }
defined class A

scala> implicitly[A { type T=String } <%< A { type T=Int } ]
<console>:9: error: could not find implicit value for parameter e: <%<[A{type T = String},A{type T = Int}]
              implicitly[A { type T=String } <%< A { type T=Int } ]
                        ^

scala> implicitly[A { type T=String } <%< A { type T=String } ]
res1: <%<[A{type T = String},A{type T = String}] = <function1>

scala> implicitly[A { type T=String } <%< A ]
res3: <%<[A{type T = String},A] = <function1>
pedrofurla
  • 12,763
  • 1
  • 38
  • 49
2

As you guessed, abstract type members are a compile time information that will be erased during type erasure. Here is a work around that uses implicit parameters. The dispatch is static.

scala> class A {
     |   type T
     | }
defined class A

scala> implicit def distString(a: A { type T = String }) = "String"
distString: (a: A{type T = String})java.lang.String

scala> implicit def distInt(a: A { type T = Int }) = "Int"
distInt: (a: A{type T = Int})java.lang.String

scala> implicit def distOther[O](a: A { type T = O }) = "Other"
distOther: [O](a: A{type T = O})java.lang.String

scala> def distinguish(a: A)(implicit ev: A { type T = a.T } => String) = ev(a)
distinguish: (a: A)(implicit ev: A{type T = a.T} => String)String

scala> distinguish(new A { type T = String })
res2: String = String

scala> distinguish(new A { type T = Int })
res3: String = Int

scala> distinguish(new A { type T = Float })
res4: String = Other 

Another way:

scala> def dist(a: A)(implicit s: a.T =:= String = null, i: a.T =:= Int = null) =
     |   if (s != null) "String" else if (i != null) "Int" else "Other"
dist: (a: A)(implicit s: =:=[a.T,String], implicit i: =:=[a.T,Int])String

scala> dist(new A { type T = String })
res5: String = String

scala> dist(new A { type T = Int })
res6: String = Int

scala> dist(new A { type T = Float })
res7: String = Other

Edit:

If the above solutions do not satisfy you, and you want to reify and introspect this type information at runtime after all, you can do that as well, using something called Manifests.

scala> :paste
// Entering paste mode (ctrl-D to finish)

abstract class A {
  type T
  def man: Manifest[T]
}

object A {
  def apply[X](implicit m: Manifest[X]) = new A { type T = X; def man = m }
}


// Exiting paste mode, now interpreting.

defined class A
defined module A

scala> def disti(a: A): String = a match {
     |   case _ if a.man <:< manifest[String] => "String"
     |   case _ if a.man <:< manifest[Int] => "Int"
     |   case _ => "Other"
     | }
disti: (a: A)String

scala> disti(A.apply[String])
res14: String = String

scala> disti(A.apply[Int])
res15: String = Int

scala> disti(A.apply[Float])
res16: String = Other
missingfaktor
  • 90,905
  • 62
  • 285
  • 365
1

side question: Because type fields are translated to parameter types in Java ?

There are no type parameters in bytecode, so type fields could not possibly be translated into them. Type fields only exist during compilation.

Alternative : We can alternativley create an enumeration and put an additional field in the class A in order to distinguish the objects. But this smells, because it would mean, that we re-implement the type system.

There's only one kind of type question you can make at run time: what class is this? Scala 2.10 gives a bit more information, but it still boils down to that question.

If you actually subclass A, you can get that information through reflection (on 2.10). Otherwise, I don't think so -- I've tested on REPL, and there it doesn't work, but things compiled to class files have more information on them.

Any other question has to be made about values.

Question : Is there a way to differ between the types of the objects with the help of the type field ? And if not, what will be a good workaround ?

No, unless Scala 2.10, reflection, and subclasses. Good workaround? Use values. The usual way to handle it is not enumeration, though, but Manifest, which can be generated implicitly.

Look up questions about how to get around type erasure in Scala.

Community
  • 1
  • 1
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • Well sure type fields must be somehow represented in compiled class files? Otherwise how would I refer them when they appear in a compiled library? There must be some information that carries the name and the bounds, no? – 0__ Jul 03 '12 at 18:48
  • @Sciss There is. If you use `javap -s` on a class file generate by Scala, you'll see a constant ScalaSig followed by a constant with a binary package representation of the type. What is missing is the ability to decode it. As to whether an anonymous class would have that information, that I have no idea. – Daniel C. Sobral Jul 03 '12 at 21:13