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!
I am not exactly sure what you want to do, but the problem you are facing is because the type of your
myTraitList
is similar to having aList[MyTrait[Any]]
. Since the type is_
the compiler doesn't know if it'sBar
orFoo
. Thankfully Scala provides Pattern Matching that allows you do condition on the type of an object.Solution 1
Solution 2