Scala MergeLatest的默认值

Scala MergeLatest的默认值,scala,akka,akka-stream,Scala,Akka,Akka Stream,MergeLatest的官方声明: MergeLatest emits列表,用于从某个输入流发出的每个元素,但仅在每个输入流发出至少一个元素之后 我的问题是:这能被绕过吗? 例如,我们是否可以提供一个默认值,使其在从任何输入流接收到至少一个元素后立即开始生成列表 以下是新的行为: (1,0,0) (2,0,0) (2,1,0) (2,1,1) (2,1,2) 而不是: (2,1,1) (2,1,2) 由于我需要将每个传入流的第一个列表也推送到输出流,您可以使用Source.single(0)

MergeLatest
的官方声明:

MergeLatest emits列表,用于从某个输入流发出的每个元素,但仅在每个输入流发出至少一个元素之后

我的问题是:这能被绕过吗? 例如,我们是否可以提供一个默认值,使其在从任何输入流接收到至少一个元素后立即开始生成列表

以下是新的行为:

(1,0,0)
(2,0,0)
(2,1,0)
(2,1,1)
(2,1,2)
而不是:

(2,1,1)
(2,1,2)

由于我需要将每个传入流的第一个列表也推送到输出流,您可以使用
Source.single(0).concat
发出零,然后发出流的其余部分:

def withInitialValue[A](source: Source[A, NotUsed], a: A): Source[A, NotUsed] =
  Source.single(a).concat(source)

不幸的是,
mergeLatest
没有提供这样的选项。而且似乎没有任何流操作符能轻易做到这一点。一种方法是根据具体需要重新调整用途。好消息是,必要的代码更改非常简单,因为相关的代码实现是
uniformfanishape
的标准
GraphStage

import akka.stream.scaladsl._
import akka.stream.stage.{ GraphStage, GraphStageLogic, InHandler, OutHandler }
import akka.stream.{ Attributes, Inlet, Outlet, UniformFanInShape }
import scala.collection.immutable

object MergeLatestWithDefault {
  def apply[T](inputPorts: Int, default: T, eagerComplete: Boolean = false): GraphStage[UniformFanInShape[T, List[T]]] =
    new MergeLatestWithDefault[T, List[T]](inputPorts, default, eagerComplete)(_.toList)
}

final class MergeLatestWithDefault[T, M](val inputPorts: Int, val default: T, val eagerClose: Boolean)(buildElem: Array[T] => M)
    extends GraphStage[UniformFanInShape[T, M]] {
  require(inputPorts >= 1, "input ports must be >= 1")

  val in: immutable.IndexedSeq[Inlet[T]] = Vector.tabulate(inputPorts)(i => Inlet[T]("MergeLatestWithDefault.in" + i))
  val out: Outlet[M] = Outlet[M]("MergeLatestWithDefault.out")
  override val shape: UniformFanInShape[T, M] = UniformFanInShape(out, in: _*)

  override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
    new GraphStageLogic(shape) with OutHandler {
      private val activeStreams: java.util.HashSet[Int] = new java.util.HashSet[Int]()
      private var runningUpstreams: Int = inputPorts
      private def upstreamsClosed: Boolean = runningUpstreams == 0
      private val messages: Array[Any] = Array.fill[Any](inputPorts)(default)

      override def preStart(): Unit = in.foreach(tryPull)

      in.zipWithIndex.foreach {
        case (input, index) =>
          setHandler(
            input,
            new InHandler {
              override def onPush(): Unit = {
                messages.update(index, grab(input))
                activeStreams.add(index)
                emit(out, buildElem(messages.asInstanceOf[Array[T]]))
                tryPull(input)
              }

              override def onUpstreamFinish(): Unit = {
                if (!eagerClose) {
                  runningUpstreams -= 1
                  if (upstreamsClosed) completeStage()
                } else completeStage()
              }
            })
      }

      override def onPull(): Unit = {
        var i = 0
        while (i < inputPorts) {
          if (!hasBeenPulled(in(i))) tryPull(in(i))
          i += 1
        }
      }

      setHandler(out, this)
    }

  override def toString = "MergeLatestWithDefault"
}

作为奖励,虽然
mergeLatest
仅在Akka Stream
2.6
+上可用,但根据我的简短测试,此重新调整用途的代码在
2.5
上似乎运行良好。

这不是最优雅的解决方案,但似乎可以运行。也有可能在流动的中间抛出单个零点吗?我现在有一个与整数完全不同的源,所以不能把它放在那里。在它到达GraphDSL中的合并组件之前,我必须将它添加到流组件(而不是源组件)。
import akka.actor.ActorSystem

object CustomMerge {

  def main(args: Array[String]): Unit = {

    implicit val system = ActorSystem("system")

    val s1 = Source(1 to 3)
    val s2 = Source(11 to 13).throttle(1, 50.millis)
    val s3 = Source(101 to 103).throttle(1, 100.millis)

    Source.combine(s1, s2, s3)(MergeLatestWithDefault[Int](_, 0)).runForeach(println)
  }
}

// Output:
//
// List(1, 0, 0)
// List(1, 11, 0)
// List(1, 11, 101)
// List(2, 11, 101)
// List(2, 12, 101)
// List(3, 12, 101)
// List(3, 13, 101)
// List(3, 13, 102)
// List(3, 13, 103)