如果系统中的某些实体可以充当数据或事件的生产者,而其他实体可以充当消费者,那么将这些“正交关注点”外部化到生产者和消费者类型类中是否有意义?
我可以看到Haskell管道库使用这种方法,并且欣赏这个问题对于来自Haskell背景的人来说可能看起来非常基本,但是会对Scala视角和示例感兴趣,因为我看不到很多。
答案 0 :(得分:2)
你应该看看Matt Might的this article。
它为您提供了Producer
,Consumer
,Transducer
(您提到的haskell库中的Pipe)的简单实现,以及如何使用它们创建Web服务器的示例。
基本上每个Producer
扩展Runnable
并有一个私有缓冲区来输出元素。缓冲区是java ArrayBlockingQueue
,它是线程安全的。
每个Consumer
也是Runnable
,并且具有使用类似架构的输入缓冲区。
当您将Consumer
链接到Producer
时,您会创建另一个Runnable
。
一开始,它将启动Producer
和Consumer
(它们是Runnable)并将在它们之间传输数据。
当您将Transducer
链接到Producer
时,会创建一个新的Producer
。
因此,如果您遵循他的实现,您应该能够以haskell的方式编写:
listen ==> connect ==> process ==> reply
以下是从上面的链接中复制和改进的一些代码:
import java.util.concurrent.ArrayBlockingQueue
trait Coroutine extends Runnable {
def start() {
val myThread = new Thread(this)
myThread.start()
}
}
trait Producer[O] extends Coroutine {
private val outputs = new ArrayBlockingQueue[O](1024)
protected def put(output: O): Unit = outputs.put(output)
def next(): O = outputs.take()
def ==>[I >: O](consumer: Consumer[I]): Coroutine = {
val that = this
new Coroutine {
def run() {
while (true) { val o = that.next(); consumer.accept(o) }
}
override def start() {
that.start()
consumer.start()
super.start()
}
}
}
}
trait Consumer[I] extends Coroutine {
private val inputs = new ArrayBlockingQueue[I] (1024)
def accept(input : I): Unit = inputs.put(input)
protected def get(): I = inputs.take()
}
以下是您可以使用它的方法:
case class IntProducer(zero: Int) extends Producer[Int]{
def run(): Unit = {
var i = zero
while(true) { put(i); i += 1 }
}
}
object Printer extends Consumer[Any]{
def run(): Unit = {
while(true) { println(get()) }
}
}
val pip = IntProducer(0) ==> Printer
pip.start()
要查看更多示例以及如何处理`传感器,请查看my Gist。