Hello, looking for some guidance as I am new to Ko...
# arrow
c
Hello, looking for some guidance as I am new to Kotlin coroutines/arrow. To get better, I am planning on doing advent of code 2022 using arrow coroutines. To start, I solved last year's day one here: https://github.com/chiroptical/advent-of-code-2022/blob/main/src/main/kotlin/dayOne/DayOne.kt. First, does this look reasonable? I am mostly curious about my approach to
CoroutineScope.windows
and
dayOne
(there are edge cases in
strictlyIncreasing
that aren't important for this conversation). Second, is it possible to fold in parallel as well? I tried creating two actors, passing inputs to them, and then combining them at the end but it just didn't do anything. I am incredibly open to suggestions here🧵
It is possible the actor example blocked indefinitely due to a surrounding
coroutineScope
on
CoroutineScope.windows
that I added because IntelliJ suggested it while I was re-writing everything.
I am planning on trying the Actors version again soon, but need a break from the computer for a bit.
Also, if anyone has a suggestion for "Kotlin coroutines by example" that would be great. I haven't found a great resource for practice.
This was posted in #feed yesterday: https://leanpub.com/coroutines. Seems potentially useful. Anyone read it?
Looks like I can swap
parMap
for
parMapUnordered
and get a boost.
One idea would be to chunk the results and reduce the chunks first? I don't see any easy way to do that in https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow
Oh, realized there is really no need for the producer here. I can just use a flow
s
Hey @chiroptical, You can also safely read lines into
Flow
relying on the automatic back-pressure from
Flow
. I talked about it this week on KotlinDevDay, will publish and share slides here in a sec 😉 Some improvements, you can safely rely on this instead of
CoroutineScope
+
produce
.
parMap
+
fold
is probably best solution here.
Copy code
fun windows(numElements: Int = 2): Flow<List<String>> = flow {
  Path("inputFiles/dayOne.test.txt")
    .useLines { lines ->
      lines.chunked(numElements).forEach { emit(it) }
    }
}
c
This is lovely. New to the Java ecosystem so seeing some examples helps a lot. Thanks!
I read in a SO comment that
flowOn(<http://Dispatchers.IO|Dispatchers.IO>)
can unblock the main thread. So, if I was to have a
windows
and
lines
Flow<T>
, would you say these are more efficient?
Copy code
fun lines(path: Path) =
    path.useLines { lines -> lines.asFlow().flowOn(<http://Dispatchers.IO|Dispatchers.IO>) }

fun windows(numElements: Int = 2, path: Path) = flow {
    path.useLines { lines ->
        lines.chunked(numElements).forEach { emit(it) }
    }
}.flowOn(<http://Dispatchers.IO|Dispatchers.IO>)
Oh,
chunked
doesn't quite work for the rolling windows but I imagine they have a function for that.
Yeah, looking for
windowed
but it was easy to find now that I have seen
chunked
!
s
There is only need for 1 call to
<http://Dispatchers.IO|Dispatchers.IO>
, everything "above" the call to
flowOn
will run on
IO
.
You can potentially also call it at the very end, and your entire flow will run on
IO
. It will unblock the
main
thread yes.
Here is the slide-deck, https://speakerdeck.com/nomisrev/functional-flowing. The recordings will probably be uploaded next week. https://speakerdeck.com/nomisrev/functional-flowing
c
Awesome. Excited to check this out. Thanks Simon!
s
My pleasure @chiroptical ☺️
c
One last question, if you want to do the fold together with parMap would you use a
MutableStateFlow
?
s
Not sure what you mean, creating a
parFold
function? This is inspired from the original impl, but written from Slack so potentially incorrect. https://github.com/Kotlin/kotlinx.coroutines/blob/6dfabf763fe9fc91fbb73eb0f2d5b488[…]43f1/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt To be honest I'd still prefer
parMap
+
fold
. This breaks back-pressure on
collect
Copy code
public suspend inline fun <T, R> Flow<T>.parFoldMap(
    ctx: CoroutineContext = EmptyCoroutineContext,
    initial: R,
    crossinline operation: suspend (value: T) -> R
    combine: suspend (R, R) -> R
): R {
 val accumulator: Atomic<R> = Atomic(initial)
 coroutineScope {
    collect { value ->
        launch {
           val res = operation(value)
           accumulator.update { acc -> acc + res }
        }
    }
  } 
  return accumulator.get()
}
This impl also results in
Unordered
c
Cool. I was just thinking about others ways to do this. I like the
parMap
+
fold
solution. It’s pretty readable too