Scala 中带有泛型的类型安全可组合构建器链

Typesafe composable builder chain in Scala with generics

我正在尝试构建一种模式,用户可以通过该模式实现一个简单的接口,该接口采用一种类型的对象和 returns 另一种类型的对象,然后还有某种类型的链对象,它由一系列这些对象组成变换。

我遇到的问题是在 Scala 中获取正确的泛型类型 - 我的 Scala-foo 还没有那么高,所以非常感谢任何建议,包括告诉我我正在这样做 wrong/non-scala方式!

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)
  }
}

因此,示例用法为:

ComposableBuilder(Seq(
    ModelEnricher(),
    CollateRecordsByKey(),
    RecordBuilder(),
)).execute(input)

很明显,对于任何连续对 builder[0].output == builder[1].input

中的构建器序列,存在一个隐含的约束

我不确定您为什么要使用存储 Class 信息的变量。仅使用标准泛型,解决方案应该简单得多:

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))
}

然后你可以让你的建造者:

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 }

并使用它们:

val builder = ComposedBuilder(ComposedBuilder(Int2DoubleBuilder, Double2StringBuilder), StringPadBuilder)
builder.process(423)
// 000423.00

Samir 的评论很有道理。如果你的情况真的这么简单,那么你可以使用内置的 Function1 特性来免费获得一些不错的功能。所以你可以让每个构建器实现一个 A => B 函数:

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 }

并使用它们:

val builder = Int2DoubleBuilder andThen Double2StringBuilder andThen StringPadBuilder
builder(423)
// 000423.00