如何使用Scala优雅地实现管道模式

时间:2012-03-07 01:03:25

标签: scala pipeline

我正在寻找使用Scala构建管道模式。我希望在编写管道对象后,它们可以像这样连接在一起:

Pipeline1 :: Pipeline2 :: Pipeline3 ...

到目前为止,我已经尝试了一些想法。有些工作,有些则没有。但它们似乎都没有完全摆脱样板代码。以下是我最接近的。

首先定义Pipeline和Source抽象类:

// I is the input type and O is the output type of the pipeline
abstract class Pipeline[I, +O](p: Pipeline[_, _ <: I]) {

  val source = p
  val name: String
  def produce(): O
  def stats():String
}
abstract class Source[+T] extends Pipeline[AnyRef, T](null)

接下来,我创建了两个管道并尝试将它们链接在一起

// this creates a random integer
class RandomInteger extends Source[Int] {
  override val name = "randInt"

  def produce() = {
    scala.Math.round(scala.Math.random.asInstanceOf[Float] * 10)
  }

  def stats()="this pipeline is stateless"
}

// multiply it by ten
class TimesTen(p: Pipeline[_, Int]) extends Pipeline[Int, Int](p) {
  private var count = 0 // this is a simple state of the pipeline
  override val name = "Times"
  def produce = {
    val i = source.produce()
    count += 1 // updating the state
    i * 10
  }
  def stats() = "this pipeline has been called for " + count + " times"
}

object TimesTen {
  // this code achieves the desired connection using ::
  // but this has to be repeated in each pipeline subclass. 
  // how to remove or abstract away this boilerplate code? 
  def ::(that: Pipeline[_, Int]) = new TimesTen(that)
}

这是连接两个管道的主类。

object Pipeline {
  def main(args: Array[String]) {
    val p = new RandomInteger() :: TimesTen
    println(p.source)
    for (i <- 0 to 10)
      println(p.produce())
    println(p.stats())
  }
}

所以这段代码有效。但我必须在我编写的每个管道类中重复TimesTen伴侣对象中的代码。这当然是不可取的。有没有更好的方法来做到这一点?反思可能有用,但我听到了关于它的坏事,比如涉及反射的任何东西都是糟糕的设计。我也不确定斯卡拉对反思的支持。

感谢您的时间。

更新:我设计了这个玩具问题,以便于理解。作为一般解决方案,并且正如我的应用程序所要求的那样,每个管道对象都有一个状态,理想情况是封装在对象本身内,而不是暴露给每个其他管道。我修改了上面的代码来反映这一点。我希望有一个基于对象的解决方案。我还在试验,如果找到的话我会告诉你的。

更新2 :经过一番思考后,我认为管道的想法实际上只是一个包含一些内部状态的通用函数,以及组成Function0函数的能力。一个Function1函数。在Scala中,Function0类没有compose()andThen()方法。

4 个答案:

答案 0 :(得分:8)

除非我遗漏了什么,否则你的管道对象只是函数,而你的::运算符只是“撰写”

val randomInteger: ()=>Int = () => scala.Math.round(scala.Math.random.asInstanceOf[Float] * 10)
val timesTen :Int => Int = x => x*10    
val pipeline: () =>Int = timesTen compose randomInteger

你的“produce()”方法只是“apply()”,但通常使用它的缩写“()”。少量的Library pimping允许您使用运算符进行合成。这是面向对象样板文件真正妨碍简单功能概念的案例之一。幸运的是,Scala可以让你避开像这样的很多用例的样板。

答案 1 :(得分:6)

以下是使用andThen的对象解决方案。我们的想法是使用输入Function1强制创建Unit个对象。连接两个管道会创建一个具有两个功能的新管道。此解决方案允许管道具有内部状态。

进一步简化是使用apply()而不是produce()。这留给读者练习。

abstract class Pipeline[-I, +O] {

  val name: String
  def produce : I => O
  def stats(): String

  def ->[X](seg:Pipeline[_ >: O, X]):Pipeline[I, X] = {
    val func = this.produce
    val outerName = this.name
    new Pipeline[I, X] {
      val name = outerName + "." + seg.name
      def produce = func andThen seg.produce 
      def stats = seg.stats
    }
  }
}

abstract class Source[+T] extends Pipeline[Unit, T] {
}

class RandomInteger extends Source[Int] {
  override val name = "randInt"
  def produce: Unit => Int = (x:Unit) => scala.Math.round(scala.Math.random.asInstanceOf[Float] * 10) 
  def stats() = "stateless"
}

class TimesTen() extends Pipeline[Int, Int] {
  private var count = 0
  override val name = "times"
  def produce : Int => Int = (x:Int) => {    
    count += 1
    x * 10
  }
  def stats() = "called for " + count + " times"
}


object Main {
  def main(args: Array[String]) {
    val p = new RandomInteger() -> new TimesTen() 

    for (i <- 0 to 10)
      println(p.produce())
    println(p.name)    // print "randInt.times"
    println(p.stats()) // print "called for 11 times"
  }
}

答案 2 :(得分:3)

object Pipelining {  implicit def toPipe[T](x : T) = new {     def :: [U](f : T => U) = f(x)  }}

import Pipelining._
List(2,3,4) :: (_.map(_*3)) :: (_.map(_.toString)) :: println 

所有学分为StephaneLD“|&gt;运算符,如F#”

http://www.scala-lang.org/node/8747

答案 3 :(得分:0)

您的意思是数据流或功能反应式编程?试试this question。反应性库正在积极开发 - 我不知道其余的。