我正在寻找使用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()
方法。
答案 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#”
答案 3 :(得分:0)
您的意思是数据流或功能反应式编程?试试this question。反应性库正在积极开发 - 我不知道其余的。