Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Functional Flowing

Functional Flowing

KotlinX’s Flow data type is extremely powerful, and offers an API that is quite familiar for functional programmers.
This talk will showcase how we can leverage KotlinX Flow to describe powerful programs, and build pipelines to transform and manipulate data in an efficient streaming way.

Simon Vergauwen

November 26, 2022
Tweet

More Decks by Simon Vergauwen

Other Decks in Programming

Transcript

  1. Creating Flows class Circle(val count: Int) suspend fun FlowCollector<Circle>.produceCircles(): Unit

    { var counter: Int = 0 while(true) { val circle = Circle(counter>+) println("Creating $circle") emit(circle) } }
  2. Creating Flows class Circle(val count: Int) suspend fun FlowCollector<Circle>.produceCircles(): Unit

    { var counter: Int = 0 while(true) { val circle = Circle(counter>+) println("Creating $circle") emit(circle) } } val circles: Flow<Circle> = flow { produceCircles() }
  3. Creating Flows class Circle(val count: Int) val circles: Flow<Circle> =

    flow { var counter: Int = 0 while(true) { val circle = Circle(counter>+) println("Creating $circle") emit(circle) } }
  4. Pipeline class Square private constructor(val circle: Circle) { companion object

    { suspend fun Circle.toSquare(): Square { print("Creating Square($count)") delay(1.seconds) return Square(this).also { println("Created $this") } } } }
  5. Putting it all together fun main() = runBlocking { circles

    .take(3) .collect { circle -> circle.toSquare() } } Creating Circle@63e31ee Creating Square(0) Created Square@6adca536 Creating Circle@23223dd8
  6. Reasoning about Flows suspend fun FlowCollector<Circle>.produceCircles(): Unit { var counter:

    Int = 0 while(true) { val circle = Circle(counter>+) println("Creating $circle") emit(circle) } }
  7. Reasoning about Flows class Square private constructor(val circle: Circle) {

    companion object { suspend fun Circle.toSquare(): Square { print("Creating Square($count)") delay(1.seconds) return Square(this).also { println("Created $this") } } } }
  8. Flowing resources fun Path.readAll(): Flow<String> = flow { >/ this:

    FlowCollector<String> useLines { lines -> lines.forEach { line -> emit(line) } } }
  9. Flowing resources /** Java SDK */ class BlobStorage(val blobName: String)

    : AutoCloseable { fun uploadBlob(data: ByteArray): CompletableFuture<UploadResult> override fun close() = Unit } suspend fun BlobStorage.insertIntoBlobStorage(line: String): UploadResult = uploadBlob(line.toByteArray(Charsets.UTF_8)).await()
  10. Flowing resources suspend fun uploadData(path: String): Unit = BlobStorage(path).use {

    storage -> Path(path) .readAll() .collect { line -> storage.insertIntoBlobStorage(line) } }
  11. Flowing resources suspend fun uploadData2(path: String): Flow<UploadResult> = blobStorage().flatMapConcat {

    blobStorage -> Path("data.txt") .readAll() .map { line -> blobStorage.insertIntoBlobStorage(line) } }
  12. Parallel Processing suspend fun uploadData3(path: String): Flow<UploadResult> = blobStorage().flatMapConcat {

    blobStorage -> Path("data.txt") .readAll() .parMapUnordered(concurrency = 3) { line -> blobStorage.insertIntoBlobStorage(line) } }
  13. Parallel Processing @FlowPreview public inline fun <A, B> Flow<A>.parMapUnordered( concurrency:

    Int = DEFAULT_CONCURRENCY, crossinline transform: suspend (a: A) >> B ): Flow<B> { val flowOfFlows: Flow<Flow<B>> = map { o -> flow { emit(transform(o)) } } return parallelFlows.flattenMerge(concurrency) }
  14. Parallel Processing suspend fun uploadData3(path: String): Flow<UploadResult> = blobStorage().flatMapConcat {

    blobStorage -> Path("data.txt") .readAll() .parMap(concurrency = 3) { line -> blobStorage.insertIntoBlobStorage(line) } }