jeudi 18 juin 2015

Collections of Traits with types generics in Scala 2.10

I'm trying to build collections of objects, defined at run time and using type generics, by referring to their superclass (well, trait), but I'm having a tough time transforming them back into the child objects. Some example code and results:

trait MyTrait[T] {
  def f(x: T): Int
}

class Foo extends MyTrait[Double] {
  def f(x: Double) = 1
}

class Bar extends MyTrait[String] {
  def f(x: String) = 2
}

val fooInstance: Foo = new Foo
val barInstance: Bar = new Bar
val myTraitList: List[MyTrait[_]] = List(fooInstance, barInstance)

println(fooInstance.getClass)
// prints "class MyExample$Foo"
println(barInstance.getClass)
// prints "class MyExample$Bar"

println(myTraitList(0).getClass)
// prints "class MyExample$Foo", so the list is preserving object classes and not just "MyTrait[_]"
println(myTraitList(1).getClass)
// prints "class MyExample$Bar", again, preserving object class

println(fooInstance.f(1.0))
// prints "1"
println(barInstance.f("blah"))
// prints "2"

println(myTraitList(0).f(1.0))
// this is where things break:
// "type mismatch; found : Double(1.0) required: _$3 where type _$3"
// so, the list element knows it's an instance of Foo (as indicated by getClass), but has lost the overloaded definition of f

println(myTraitList(1).f("blah"))
// the same error occurs on the Bar instance:
// "type mismatch; found : String("blah") required: _$3 where type _$3"

If I hard-code a myTraitList(0).asInstanceOf[Foo].f(1.0), it (predictably) works just fine. So, I tried to create a function to do that at run time:

def castToCorrectChildClass(o: MyTrait[_], label: Char): MyTrait[_] = {
  return label match {
    case 'f' => o.asInstanceOf[Foo]
    case 'b' => o.asInstanceOf[Bar]
    case _ => o
  }
}

Unfortunately, this is subject to the same problems:

println(castToCorrectChildClass(myTraitList(0), 'f').f(1.0))
// type mismatch; found : Double(1.0) required: _$2 where type _$2

One solution is to make a List[MyTrait[Any]] to store the instances and make the type parameter covariant: trait MyTrait[+T]. Unfortunately, in my real-world code, I need it to be invariant for other reasons, so I can't use this workaround. I've also tried using ClassTags and TypeTags to remember the child class thinking this were a reflection-related problem, but I didn't have any luck (and I suspect that that's not the intended use case for these, anyway, though perhaps I'm mistaken).

Any suggestions? I'd love to have a flexible collection of unknown number (so, no Tuples) of child objects extending the same trait but with different types as per user input gathered at run time, and I'm happy to accept the trade-off of doing bookkeeping to ensure I don't mis-cast (or mis-asInstanceOf) an object back to the incorrect type, but I can't seem to get it working. Thanks in advance!





Aucun commentaire:

Enregistrer un commentaire