Reputation: 700
I'm trying to construct a pattern whereby users can implement a simple interface which takes one type of object and returns another, and then also have some type of chain object which consists of a sequence of these transforms.
The problem I'm having is getting the correct generic types in Scala - my Scala-foo is not that high yet so any advice is most appreciated, including telling me I'm doing this the wrong/non-scala way!
trait Builder[INPUT, OUTPUT] {
var input: Class[INPUT]
var output: Class[OUTPUT]
def process(input: RDD[INPUT]): RDD[OUTPUT]
}
class ComposableBuilder[INPUT, OUTPUT](input: Class[INPUT], output: Class[OUTPUT], phases: Seq[Phase[Any, Any]]) {
def appendBuilder[U](phase: Phase[OUTPUT, U]): ComposableBuilder[INPUT, U] = {
new ComposableBuilder[INPUT, U](input.class, phase.output.class, phases :+ phase)
}
}
So that an example usage would be:
ComposableBuilder(Seq(
ModelEnricher(),
CollateRecordsByKey(),
RecordBuilder(),
)).execute(input)
So clearly there's an implied constraint that for the sequence of builders in that for any consecutive pair builder[0].output == builder[1].input
Upvotes: 1
Views: 599
Reputation: 52681
I'm not sure why you are using variables that store Class
information. The solution should be much simpler just using the standard generics:
trait Builder[A,B] {
def process(input: A): B
}
case class ComposedBuilder[A,B,C](b1: Builder[A,B], b2: Builder[B,C]) extends Builder[A,C] {
def process(input: A): C = b2.process(b1.process(input))
}
Then you can make your Builders:
object Int2DoubleBuilder extends Builder[Int, Double] { def process(input: Int): Double = input.toDouble }
object Double2StringBuilder extends Builder[Double,String] { def process(input: Double): String = f"$input%.2f" }
object StringPadBuilder extends Builder[String,String] { def process(input: String): String = "000" + input }
And use them:
val builder = ComposedBuilder(ComposedBuilder(Int2DoubleBuilder, Double2StringBuilder), StringPadBuilder)
builder.process(423)
// 000423.00
Samir's comment makes a good point. If your situation is truly this straightforward, then you could use the built-in Function1
trait to get some nice features for free. So you could have each builder implement a A => B
function:
object Int2DoubleBuilder extends (Int => Double) { def apply(input: Int): Double = input.toDouble }
object Double2StringBuilder extends (Double => String) { def apply(input: Double): String = f"$input%.2f" }
object StringPadBuilder extends (String => String) { def apply(input: String): String = "000" + input }
And use them:
val builder = Int2DoubleBuilder andThen Double2StringBuilder andThen StringPadBuilder
builder(423)
// 000423.00
Upvotes: 6