Question

Consider this (contrived) code :

sealed abstract class MyCaseClass(val num : Long)
case class CaseOne(override val num : Long) extends MyCaseClass(num)
case class CaseTwo(override val num : Long) extends MyCaseClass(num)

val groupedObs : Observable[(Long, Observable[MyCaseClass])] =
    Observable(1 to 20) map {
      x => if (x > 10) CaseOne(x) else CaseTwo(x)
  } groupBy {
    case CaseOne(x) => x % 2 == 0
    case CaseTwo(x) => x % 2 == 0
  }

groupedObs subscribe ( (onNext : (Long,Observable[MyCaseClass])) => onNext match {
        case (even : Boolean, o : Observable[CaseOne] ) => println("Case one")
        case (even : Boolean, o : Observable[CaseTwo] ) => println("Case two")
    }
)

It creates an observable, where the first 10 are CaseOne and the next are CaseTwo. It then groups them by whether they are even or odd. So, the grouped observable ends up being of type Observable[(Long, Observable[MyCaseClass])], as per the RX-Java spec.

However, when we come to subscribe to a 'groupedby' observable, type erasure means the signature of the nested Observable is lost i.e. we can't tell whether it is CaseOne or CaseTwo (it becomes of type Any) - the compiler warns about this, and so the output is

Case one
Case one
Case one
...

My question is, in the above scenario, how do you handle the type erasure of the nested Observable?

My only workaround so far has been to include an extra value in the key which is used to identify the nested Observable type, and then to cast (using asInstance) to this type. But this isn't very nice.

Also note, although I don't use even in this example, it directly reflects the structure of my problem.

Was it helpful?

Solution

Note that all observables contained in the result of groupedObs are Observable[MyCaseClass] and not Observable[CaseOne] or Observable[CaseTwo]. So if there was no erasure and the type check did work you would get MatchError instead. In fact, in your case observables under each key will include both CaseOne and CaseTwo elements.

So yes, you need to include the extra data. You have two options.

1) Make the classes part of the key:

val groupedObs : Observable[((Boolean, Class[_]), Observable[MyCaseClass])] =
    Observable(1 to 20) map {
      x => if (x > 10) CaseOne(x) else CaseTwo(x)
  } groupBy {
    case CaseOne(x) => (x % 2 == 0, classOf[CaseOne])
    case CaseTwo(x) => (x % 2 == 0, classOf[CaseTwo])
  }

groupedObs subscribe ( (onNext : ((Boolean, Class[_]), Observable[MyCaseClass])) => onNext match {
        case ((even : Boolean, c: Class[_]), o : Observable[_]) if c == classOf[CaseOne] => println("Case one")
        case ((even : Boolean, c: Class[_]), o : Observable[_]) if c == classOf[CaseTwo] => println("Case two")
    }
)

2) Check the elements of the nested observable:

// groupedObs as in your code, not as above
groupedObs subscribe {
  case (even : Boolean, o : Observable[MyCaseClass] ) => 
    o subscribe { 
      case _: CaseOne => println("Case one")
      case _: CaseTwo => println("Case two")
    }
}

This prints output for every element of the original observable; alternately, you can stop after the first element (e.g. because you know all elements for the same key have the same class), etc.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top