I am creating a map-reduce framework, and right now I am trying to create a builder class to instantiate the processing pipeline. This builder needs to hold a list of functions specified by the user, so later it can construct the pipeline using these functions as arguments to instantiate Worker objects.
I am using case classes to create function "holders", and also for the "workers". My problem is that when I go on to analyse the holders, if I pattern-match on them the type information from the function seems to be missing due to type erasure. Here is some minimal code that appears to reproduce the problem I am having.
trait Holders
case class MapHolder[A, B](f: A => B) extends Holders
trait Worker
case class MapWrk[A, B](f: A => B) extends Worker
object MyTypeErasureProblem extends App {
val myFunc = MapHolder((x: Int) => x + 10)
def buildWorker(hh: Holders) =
hh match {
case MapHolder(f) => MapWrk(f)
}
println(buildWorker(myFunc).f(10))
}
The compiler error is
Error:(22, 35) type mismatch;
found : Nothing => Any
required: A => Any
case MapHolder(f) => MapWrk(f)
^
Error:(26, 33) type mismatch;
found : Int(10)
required: Nothing
println(buildWorker(myFunc).f(10))
^
The problem can be solved if we define buildWorker like this:
def buildWorker[A,B](hh: MapHolder[A,B]) = ...
But I need to process different kinds of Holders, including
case class ReduceHolder[V](f: (V,V) => V) extends Holders
which, by the way, works just fine in a similar code, with no errors or warnings at all. In fact, only the generic type A=>B seems to be a problem, because the function argument becomes Nothing, passing around objects with other generic types work, e.g. Tuple2[A,B].
This looks like a problem related to type erasure to me, but how can I go around it? I have tried to ClassTag all the things, but it didn't work.
MORE INFO
Here's an update with the approach suggested by Travis, using a buildWorker method inside the Holder.
This here works the way I needed:
case class DataHolder[T](f: T) {
def buildWorker() = DataWrk[T](f)
}
case class DataWrk[T](f: T)
object MyTypeErasureProblem2 extends App {
val pipeline = List(
DataHolder[Int](10).buildWorker(),
DataHolder[String]("abc").buildWorker()
)
val result = pipeline collect {
case DataWrk(f: Int) => "Int data"
case DataWrk(f: String) => "String data"
}
result foreach println
}
Output:
Int data
String data
But if we use function types, it doesn't work anymore:
case class MapHolder[T](f: T => T) {
def buildWorker() = MapWrk[T](f)
}
case class MapWrk[T](f: T => T)
object MyTypeErasureProblem extends App {
val pipeline = List(
MapHolder[Int]((x: Int) => x + 10).buildWorker(),
MapHolder[String]((x: String) => x + "abc").buildWorker()
)
val result = pipeline collect {
case MapWrk(f: (Int => Int)) => "Int endofunction"
case MapWrk(f: (String => String)) => "String endofunction"
}
result foreach println
}
Output:
Int endofunction
Int endofunction
When we try to match on the instantiated Workers, the first case always matches.
buildWorker: Workersmethod onHolders? - Travis BrownbuildWorkerfunction to process a list ofHolders, but their types are already lost by the time we reach the part of the code we can call theirhh.buildWorkermethod. I tried something like that, but then we have to match the holders toMapHolder[Any,Any], for instance, and it seems that messes things up (I can't pattern-match the resultingMapWkrs later). - dividebyzeroMapHolder, though—its implementation ofbuildWorkercan just do the right thing. If the return type ofbuildWorkerisWorkersthere are no types to lose. - Travis Brown