带有chunk和zipWithIndex的scalaz-stream中的令人费解的行为

时间:2014-04-25 06:17:44

标签: scala scalaz scalaz7 scalaz-stream

我正在尝试使用scalaz-stream处理数据流,并且操作费用昂贵※。

scala> :paste
// Entering paste mode (ctrl-D to finish)

    def expensive[T](x:T): T = {
      println(s"EXPENSIVE! $x")
      x
    }
    ^D
// Exiting paste mode, now interpreting.

expensive: [T](x: T)T

※是的,是的,我知道混合代码与副作用是错误的函数式编程风格。 print语句只是为了跟踪被调用的昂贵()的次数。)

在将数据传递给昂贵的操作之前,我首先需要将其拆分为块。

scala> val chunked: Process[Task,Vector[Int]] = Process.range(0,4).chunk(2)
chunked: scalaz.stream.Process[scalaz.concurrent.Task,Vector[Int]] = Await(scalaz.concurrent.Task@7ef516f3,<function1>,Emit(SeqView(...),Halt(scalaz.stream.Process$End$)),Emit(SeqView(...),Halt(scalaz.stream.Process$End$)))

scala> chunked.runLog.run
res1: scala.collection.immutable.IndexedSeq[Vector[Int]] = Vector(Vector(0, 1), Vector(2, 3), Vector())

然后我将昂贵的操作映射到块流上。

scala> val processed = chunked.map(expensive)
processed: scalaz.stream.Process[scalaz.concurrent.Task,Vector[Int]] = Await(scalaz.concurrent.Task@7ef516f3,<function1>,Emit(SeqViewM(...),Halt(scalaz.stream.Process$End$)),Emit(SeqViewM(...),Halt(scalaz.stream.Process$End$)))

当我执行它时,它会调用昂贵的()预期的次数:

scala> processed.runLog.run
EXPENSIVE! Vector(0, 1)
EXPENSIVE! Vector(2, 3)
EXPENSIVE! Vector()
res2: scala.collection.immutable.IndexedSeq[Vector[Int]] = Vector(Vector(0, 1), Vector(2, 3), Vector())

但是,如果我将一个调用链接到zipWithIndex,那么很多次调用expensive():

>scala processed.zipWithIndex.runLog.run
EXPENSIVE! Vector()
EXPENSIVE! Vector()
EXPENSIVE! Vector()
EXPENSIVE! Vector()
EXPENSIVE! Vector(0)
EXPENSIVE! Vector(0)
EXPENSIVE! Vector(0)
EXPENSIVE! Vector(0)
EXPENSIVE! Vector(0, 1)
EXPENSIVE! Vector()
EXPENSIVE! Vector()
EXPENSIVE! Vector()
EXPENSIVE! Vector()
EXPENSIVE! Vector(2)
EXPENSIVE! Vector(2)
EXPENSIVE! Vector(2)
EXPENSIVE! Vector(2)
EXPENSIVE! Vector(2, 3)
EXPENSIVE! Vector()
EXPENSIVE! Vector()
EXPENSIVE! Vector()
EXPENSIVE! Vector()
EXPENSIVE! Vector()
EXPENSIVE! Vector()
res3: scala.collection.immutable.IndexedSeq[(Vector[Int], Int)] = Vector((Vector(0, 1),0), (Vector(2, 3),1), (Vector(),2))

这是一个错误吗?如果这是理想的行为,有人可以解释原因吗?如果昂贵()需要很长时间,你可以看到为什么我更喜欢使用较少的调用结果。

以下是一个包​​含更多示例的要点:https://gist.github.com/underspecified/11279251

1 个答案:

答案 0 :(得分:2)

您看到此issue,可能需要different forms。问题基本上是map可以看到(并做其中)chunk在构建结果时所采取的中间步骤。

此行为may change in the future,但与此同时还有一些可能的解决方法。最简单的方法之一就是在流程中包含昂贵的函数并使用flatMap而不是map

chunked.flatMap(a =>
  Process.eval(Task.delay(expensive(a)))
).zipWithIndex.runLog.run

另一个解决方案是将昂贵的功能包装在有效的渠道中:

def expensiveChannel[A] = Process.constant((a: A) => Task.delay(expensive(a)))

现在您可以使用through

chunked.through(expensiveChannel).zipWithIndex.runLog.run

虽然目前的行为可能有点令人惊讶,但它也是一个很好的提醒,你应该使用类型系统来帮助你跟踪所有你关心的效果(并且长时间运行的计算可以是其中之一)。