【问题标题】:How to elegantly implement the pipeline pattern using Scala如何使用 Scala 优雅地实现管道模式
【发布时间】:2012-03-07 01:03:25
【问题描述】:

我正在寻找使用 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 伴随对象中的代码。这当然是不可取的。有没有更好的方法来做到这一点?反射可能会起作用,但我听说过一些不好的事情,比如任何涉及反射的东西都是糟糕的设计。我也不确定 Scala 是否支持反射。

感谢您的宝贵时间。

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

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

【问题讨论】:

    标签: scala pipeline


    【解决方案1】:

    这是使用andThen 的对象的解决方案。这个想法是通过使用输入Unit 来强制创建Function1 对象。连接两个 Pipelines 会创建一个包含两个函数的新 Pipeline。此解决方案允许管道具有内部状态。

    进一步的简化是使用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】:

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

      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()”,但通常使用它是“()”的缩写。少量的图书馆拉皮条将允许您使用运算符进行组合。这是面向对象样板真正妨碍简单功能概念的情况之一。幸运的是,Scala 可以让您避免像这样的许多用例的样板。

      【讨论】:

      • 恕我直言对象仍然有用。管道可以有状态,可以舒适地放入对象中。我认为使用 compose 功能可以解决问题。一种解决方案是创建一个包含管道对象列表的辅助对象,并将它们组合在一起。我明天看看能不能用。
      • 您的代码无法在 2.9.1 上编译 - 它表示“randomInteger”没有“compose”方法。 (以及“andThen”方法)
      • randomInteger compose timesTen 是错误的方法。您不能将Unit 乘以十,然后将其提供给randomInteger。相反,您需要randomInteger andThen timesTentimesTen compose randomInteger。使用Function1 绝对是让这个“管道”想法发挥作用的最方便的方法。
      • @AlbertLi BTW,你可以使用 reduce 来处理大量的链式函数:(func1::func2:func3::func4::Nil).reduce{_ andThen _}
      • @om-nom-nom reduce中不需要,标准库有Function.chain[A](s: Seq[(A) =&gt; A]): (A) =&gt; A
      【解决方案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 "|> operator like in F#"

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

      【讨论】:

      • 感谢帖子,但我收到一个错误:value |&gt; is not a member of List[Int] 我在这里错过了什么?
      【解决方案4】:

      您的意思是像数据流或函数响应式编程?试试this question。反应式库正在积极开发中——我不知道其余的。

      【讨论】:

      • 我猜它更像是一个具有一些内部状态的广义函数,并且能够将 Function0 与 Function1 组合起来
      猜你喜欢
      • 1970-01-01
      • 2021-12-28
      • 2011-09-08
      • 2020-02-29
      • 1970-01-01
      • 1970-01-01
      • 2016-10-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多