Materialised value can be produced by both sink and source. One creates a runnable graph by combining a source to a sink. Keep defines which materialised value to keep when combining
Keep.right picks materialised value of the sink
Keep.left picks materialised value of the source
Keep.both picks both in the form of a tuple
Keep.none ignores both and picks NotUsed, ie a marker to indicate that there is no materialised value.
By default, Keep.left is used in operations via, to etc.
Following examples highlight this
Given a Source[Int, String] and a Sink[Int, Future[Int]]
val source: Source[Int, String] = Source(List(1, 2, 3)).mapMaterializedValue(_ => "Source Mat Value")
val sink: Sink[Int, Future[Int]] = Sink.fold(0)(_ + _)
We can combine a source and sink to create a runnable graph with different materialised values.
val left: String = source.to(sink).run() //same as toMat(...)(Keep.left)
val right: Future[Int] = source.toMat(sink)(Keep.right).run()
val both: (String, Future[Int]) = source.toMat(sink)(Keep.both).run()
Now, if we run it and print every materialised value it produces following
left=Source Mat Value
right=Future(Success(6))
both=(Source Mat Value,Future(Success(6)))
Please don't mix up materialised value with processing of elements of the stream.
Consider following fold stages
val flowFold: Flow[Int, Int, NotUsed] = Flow[Int].fold(0)(_ + _)
val sinkFold: Sink[Int, Future[Int]] = Sink.fold(0)(_ + _)
flowFold applies fold function to every element in the stream and pushes one single value representing the result of fold to downstream. This element can be further processed if needed.
Whereas, sinkFold is the final stage in a graph and it cannot push elements further downstream. It uses materialised value Future[Int] to return the fold result when graph has processed all elements and completed.
if the value of Flow.fold is 55 should this be the materialised value of the flow instead of NotUsed.
No, value 55 is not a materialised value. It's pushed as an element to downstream sink.
You can "catch" element 55 in a materialised value with the help of Sink.head
val flow: Flow[Int, Int, NotUsed] = Flow[Int].fold(0)(_ + _)
val alternativeFoldSink: Sink[Int, Future[Int]] = flow.toMat(Sink.head)(Keep.right)
Every stage can produce materialised value then (why) can't Flow.fold
generate materialised value.
Yes, every stage may produce a materialised value. But Flow.fold is designed not to do so. Most of the Flow definitions do not provide materialised values. If you want to use materialised value and fold, I'd suggest to use Sink.fold