用于增长列表的scalaz流结构

用于增长列表的scalaz流结构,scala,scalaz,akka-stream,scalaz-stream,Scala,Scalaz,Akka Stream,Scalaz Stream,我有一种预感,我可以(应该?)使用scalaz streams来解决我的问题,就像这样 我有一个起始项a。我有一个函数,它接受a并返回a的列表 def doSomething(a : A) : List[A] 我有一个以1项(起始项)开始的工作队列。当我们处理(doSomething)每个项目时,它可能会将许多项目添加到同一工作队列的末尾。然而,在某个时刻(在数百万个项目之后),我们doSomething处理的每个后续项目将开始向工作队列添加越来越少的项目,并且最终不会添加任何新项目(这些项目

我有一种预感,我可以(应该?)使用scalaz streams来解决我的问题,就像这样

我有一个起始项a。我有一个函数,它接受a并返回a的列表

def doSomething(a : A) : List[A]
我有一个以1项(起始项)开始的工作队列。当我们处理(
doSomething
)每个项目时,它可能会将许多项目添加到同一工作队列的末尾。然而,在某个时刻(在数百万个项目之后),我们
doSomething
处理的每个后续项目将开始向工作队列添加越来越少的项目,并且最终不会添加任何新项目(这些项目的doSomething将返回Nil)。这就是我们知道计算最终将终止的方式

假设scalaz streams适用于此,请告诉我实现此功能需要考虑哪些总体结构或类型

一旦用一个“工人”完成了一个简单的实现,我还希望使用多个工人并行处理队列项目,例如,拥有一个5个工人的池(每个工人将把其任务分配给代理来计算
doSomething
),因此我需要处理影响(例如工人失败)在这个算法中也是如此。

所以“如何”的答案是:

结果:

result: List[Int] = List(2, 4, 6, 1, 3, 5, 0, 2, 4, 1, 3, 0, 2, 1, 0)
但是,您可能会注意到:

1) 我必须解决终止问题。akka stream也存在同样的问题,解决这个问题要困难得多,因为您无法访问队列,也没有自然的背压来保证队列不会因为读卡器速度快而变空

2) 我不得不为输出引入另一个队列(并将其转换为
列表
),因为工作队列在计算结束时变为空

所以,这两个库都不能很好地适应这样的需求(有限流),但是scalaz流(在删除scalaz依赖项后将成为“fs2”)足够灵活,可以实现您的想法。最大的问题是,默认情况下,它将按顺序运行。有(至少)两种方法可以加快速度:

1) 将doSomething分为多个阶段,如
.flatMap(doSomething1).flatMap(doSomething2).map(doSomething3)
,然后在它们之间放置另一个队列(如果阶段花费的时间相等,则大约快3倍)

2) 并行化队列处理。Akka拥有
mapAsync
,它可以自动并行执行
map
s。Scalaz流有块——您可以将q分组为5个块,然后并行处理块中的每个元素。无论如何,这两种解决方案(akka和scalaz)都不太适合使用一个队列作为输入和输出

但是,同样,这太复杂了,而且毫无意义,因为有一种经典的简单方法:

@tailrec def calculate(l: List[Int], acc: List[Int]): List[Int] = 
  if (l.isEmpty) acc else { 
    val processed = l.flatMap(doSomething) 
    calculate(processed, acc ++ processed) 
  }

scala> calculate(List(3,5,7), Nil)
res5: List[Int] = List(2, 4, 6, 1, 3, 5, 0, 2, 4, 1, 3, 0, 2, 1, 0)
这是并行化的一个:

@tailrec def calculate(l: List[Int], acc: List[Int]): List[Int] = 
  if (l.isEmpty) acc else { 
    val processed = l.par.flatMap(doSomething).toList
    calculate(processed, acc ++ processed) 
  }

scala> calculate(List(3,5,7), Nil)
res6: List[Int] = List(2, 4, 6, 1, 3, 5, 0, 2, 4, 1, 3, 0, 2, 1, 0)
所以,是的,我想说scalaz stream和akka streams都不符合您的要求;不过,经典的scala并行集合非常适合

如果您需要跨多个JVM进行分布式计算,那么看看ApacheSpark,它的scala dsl使用相同的map/flatMap/fold样式。它允许您处理不适合JVM内存的大型集合(通过跨JVM扩展它们),因此您可以通过使用RDD而不是
列表
来改进
@tailrec def calculate
。它还将为您提供在
doSomething
中处理故障的工具

另外,这就是为什么我不喜欢使用流媒体库来完成这些任务。流更像是来自某些外部系统(如HttpRequests)的无限流,而不是预定义(甚至大)数据的计算


p.S.2如果您需要反应式(无阻塞),您可以使用
Future
(或
scalaz.concurrent.Task
)+
Future.sequence

工作队列(WQ)是一个列表[a]。当我用doSomething(每个项目返回一个列表[a])处理WQ中的每个项目时,这将以一种纯粹的功能性方式添加到WQ的末尾?谢谢@dk14。我也读了一些关于akka streams和graph dsl的文章,也许这是一个竞争者。综上所述,我们从一个类型为a的项目开始,该项目通过一个函数“doSomething”,该函数返回一个项目列表(零个或多个a项目),然后这些项目逐一反馈到同一个“doSomething”函数中。也许有一种方法可以在akka溪流中很好地表达这一点,即使用剂量测量作为流量函数?感谢@dk14花费时间来解释您所想到的各种解决方案的优点。
@tailrec def calculate(l: List[Int], acc: List[Int]): List[Int] = 
  if (l.isEmpty) acc else { 
    val processed = l.par.flatMap(doSomething).toList
    calculate(processed, acc ++ processed) 
  }

scala> calculate(List(3,5,7), Nil)
res6: List[Int] = List(2, 4, 6, 1, 3, 5, 0, 2, 4, 1, 3, 0, 2, 1, 0)