是否有一种惯用的方式以特殊的方式处理Akka流的Source
第一个元素?我现在拥有的是:
var firstHandled = false
source.map { elem =>
if(!firstHandled) {
//handle specially
firstHandled = true
} else {
//handle normally
}
}
由于
答案 0 :(得分:8)
虽然我一般会使用Ramon的答案,但您也可以使用前缀为1的prefixAndTail
和flatMapConcat
来实现类似的目标:
val src = Source(List(1, 2, 3, 4, 5))
val fst = Flow[Int].map(i => s"First: $i")
val rst = Flow[Int].map(i => s"Rest: $i")
val together = src.prefixAndTail(1).flatMapConcat { case (head, tail) =>
// `head` is a Seq of the prefix elements, which in our case is
// just the first one. We can convert it to a source of just
// the first element, processed via our fst flow, and then
// concatenate `tail`, which is the remainder...
Source(head).via(fst).concat(tail.via(rst))
}
Await.result(together.runForeach(println), 10.seconds)
// First: 1
// Rest: 2
// Rest: 3
// Rest: 4
// Rest: 5
这当然不仅适用于第一个项目,而且适用于第一个 N 项目,条件是这些项目将被视为严格的集合。
答案 1 :(得分:3)
使用zipWith
您可以使用仅在第一次返回Source
的布尔值来压缩原始true
。然后可以处理此压缩源。
首先,我们需要一个发布布尔人的来源:
//true, false, false, false, ...
def firstTrueIterator() : Iterator[Boolean] =
(Iterator single true) ++ (Iterator continually false)
def firstTrueSource : Source[Boolean, _] =
Source fromIterator firstTrueIterator
然后我们可以定义一个处理两种不同情况的函数:
type Data = ???
type OutputData = ???
def processData(data : Data, firstRun : Boolean) : OutputData =
if(firstRun) { ... }
else { ... }
然后可以在原始来源的zipWith
中使用此功能:
val originalSource : Source[Data,_] = ???
val contingentSource : Source[OutputData,_] =
originalSource.zipWith(firstTrueSource)(processData)
使用有状态流程
您可以创建一个Flow
,其中包含与问题中的示例类似但状态更为实用的状态:
def firstRunner(firstCall : (Data) => OutputData,
otherCalls : (Data) => OutputData) : (Data) => OutputData = {
var firstRun = true
(data : Data) => {
if(firstRun) {
firstRun = false
firstCall(data)
}
else
otherCalls(data)
}
}//end def firstRunner
def firstRunFlow(firstCall : (Data) => OutputData,
otherCalls : (Data) => OutputData) : Flow[Data, OutputData, _] =
Flow[Data] map firstRunner(firstCall, otherCalls)
此流程可以应用于您的原始资源:
def firstElementFunc(data : Data) : OutputData = ???
def remainingElsFunc(data : Data) : OutputData = ???
val firstSource : Source[OutputData, _] =
originalSource via firstRunFlow(firstElementFunc,remainingElseFunc)
"惯用语"
直接回答你的问题需要规定"惯用的方法"。我最后回答那个部分,因为它是编译器最不可验证的,因此更接近意见。我永远不会声称自己是惯用代码的有效分类器。
我对akka-streams的个人经验是,最好将我的视角转换为想象一个Data
元素的实际流(我想到的是带有厢式车的火车)。我是否需要将其分解为多个固定尺寸的列车?只有某些厢式车可以通过吗?我可以并排安装另外一辆包含Boolean
汽车的列车,这些汽车可以向前方发出信号吗?由于我对流(火车)的关注,我更喜欢zipWith方法。我最初的方法是始终使用连接在一起的其他流部分。
此外,我发现最好尽可能在akka Stream组件中嵌入尽可能少的代码。 firstTrueIterator
和processData
根本不依赖于akka。同时,firstTrueSource
和contingentSource
定义几乎没有逻辑。这允许您独立于笨重的ActorSystem测试逻辑,并且可以在Futures或Actors中使用guts。
答案 2 :(得分:0)
虽然我更喜欢使用zip的方法,但是也可以使用statefulMapConcat
:
source
.statefulMapConcat { _ =>
var firstRun = true
elem => {
if (firstRun) {
//first
firstRun = false
} else {
//not first
}
}
}
答案 3 :(得分:0)
您可以使用prepend
在流之前添加源。只需在流中添加单个物料源,将其排干后,其余原始源将继续。
https://doc.akka.io/docs/akka/current/stream/operators/Source-or-Flow/prepend.html
Source(List(1, 2, 3))
.prepend(Source.single(0))
.runWith(Sink.foreach(println))
0
1
2
3