<@UJHCLJVUK> I was just using `parTupled` and it c...
# arrow-contributors
s
@julian I was just using
parTupled
and it couldn't be resolved without providing
CoroutineContext
πŸ€” Did you encounter something like this while you were working on the PR?
j
This is really weird. Somehow I missed the convenience `parZip`s that allow the user to call them without providing a
CoroutineContext
. I don't know how that even happened. I didn't encounter the problem because I wasn't looking for it πŸ˜…. I only implemented the `parTupled`s that require a
CoroutineContext
, or in absence of one, force named parameters to be used. Sorry about that. I can provide another PR for the missing functions.
s
Well, those overloads shouldn't be there actually.. πŸ€” I'd prefer it if it follows the same approach as KotlinX Coroutines, since it can be that
parZip
accidentally overwrites
<http://Dispatchers.IO|Dispatchers.IO>
. I.e.
Copy code
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
  parZip({ io() }, { otherIO() }) { a, b -> ... }
}
While in contrast that doesn't happen with other functionality in KotlinX, so it's not really in line with each-other. So all the
Dispatchers.Default
overloads should actually be deprecated. They should be needed at all.. Curious if this improves the situation with
parTupled
... Or the overloading syntax with
Tuple
.. I personally think overloading
parZip
with 18 methods just for tupling is a tad much 😞 I was already a bit on the fence when it was 9 methods.
Huh, it seems that overload is really needed... 36 methods just for
parZip
and
parTupled
πŸ˜• Pfft that sucks..
I guess then there is no point in the default argument,
ctx: CoroutineContext = EmptyCoroutineContext
.
j
So, change all
parTupled
to this sort of thing:
Copy code
public suspend inline fun <A, B> parTupled(
  crossinline fa: suspend CoroutineScope.() -> A,
  crossinline fb: suspend CoroutineScope.() -> B,
):  Pair<A, B>
Or remove
parTupled
?
s
Hmm, I am a bit conflicted because it's already a very thin layer over
coroutineScope { awaitAll(async { }, ..., ...) }
. I have to periodically remind/re-check myself why we have these methods πŸ˜… TL;DR provide
awaitAll
behavior over
suspend fun
, which is consistent across
Job
and
SupervisorJob
. Running a bunch of different tests now, and I am again running in some unexpected scenarios πŸ˜… I guess
parZip
provides a simpler model for parallelism.. I need to write a long document on this, I keep forgetting details after not working with low-level KotlinX for a long time πŸ€•
Copy code
suspend fun one(): Int = delay(1000).let { 1 }
suspend fun two(): Int =
  throw RuntimeException("Boom!")

coroutineScope {
  measureTimedValue {
    kotlin.runCatching {
      val fa = async { one() }
      val fb = async { two() }
      fa.await() + fb.await()
    }
  }.let(::println)
}
I was expecting that
runCatching
here captures
RuntimeException
but the first
await
throws
JobCancellationException
, and then
coroutineScope
rethrows the
RuntimeException
. Whereas:
Copy code
coroutineScope {
  measureTimedValue {
    kotlin.runCatching {
      parZip({ one() }, { two() }) { a, b -> a + b }
    }
  }.let(::println)
}
Here I can capture the
RuntimeException
since
awaitAll
(used in
parZip
) throws
RuntimeException
and cancels-and-joins the others
async
internally first.
Similarly, if you swap
coroutineScope
with
supervisorScope
then the
supervisorScope { async { } }
snippet takes
1.second
instead of failing fast. Which is expected by
supervisorScope
but
parZip
provides consistent semantics in this case again through
awaitAll
. Although this is expected of
supervisorScope { }
when a function is defined
suspend fun CoroutineScope
(which is actually illegal to have both
suspend
and
CoroutineScope
) the behavior depends on the caller.
I wonder how much demand there is for
parZip
though πŸ€” I use it frequently, but that's probably because I am biased and used to it πŸ˜‚ It's also consistent to what I know from other FP langs.
WDYT @julian?
j
By this
when a function is defined
suspend fun CoroutineScope
do you mean an extension function with
CoroutineScope
as receiver?
s
Yes, exactly.
Oh, and it also behaves completely different when
CancellationException
is thrown. Then it's prone to hanging in some cases but
SupervisorJob
can also cause that in some edge-cases.
But I guess that shouldn't generally be an issue.. πŸ€”
j
I've not used
parZip
much, myself. But I'm probably not the typical Arrow user - I don't get to use Arrow at my job. I don't really have a strong position. Nor a strong grasp of the trade-offs, if I'm being honest.
I wish I did. Maybe that'll develop with working with you more.
s
Hmm, then perhaps we should be more conservative and remove
parTupled
for now. We can always re-raise your PR and add it later. Removing it later is harder. Well, you not using Arrow makes that you have good insight and might be able to provide good feedback here. Did you ever encounter any of the problems I described above, while working or in production and would you rather use
parZip
?
TBH I really hate throwing people's work and effort away 😞 Always makes me feel bad.
j
I really hate throwing people's work and effort away
Thank you for caring. It's fine, though. Sometimes one must do a thing to really know what a thing means in context. That's just life, right? πŸ˜„
s
Yes, that is absolutely right! We've been working for the past 4 years simplifying Arrow, and throwing away all our previous work away πŸ˜… Also, why am I am a bit more reserved with adding new code. It's a blade that cuts from two sides.
Hmm, that idiom is translated a bit too directly πŸ˜‚ A double edged sword?
j
Yes, double-edged sword. But I got your meaning πŸ˜„
s
Btw, we're moving the meeting to 4pm CEST so it's more time friendly for everyone. The person that created the meeting is on long PTO, so it didn't happen today. We're hoping to send out a new invitation by next meeting in 2 weeks πŸ˜‰
j
To answer your earlier question, I think I would rather use
parZip
if I was unsure about how
awaitAll
behaves relative to
coroutineScope
vs
supervisorScope
. Or I was pressed for time and didn't want to have to experiment to find out. But folks more expert in coroutines might opt to be more explicit/transparent and skip
parZip
in favor of
awaitAll
.
Especially if they're confident of the expertise of their code-reading audience.
we're moving the meeting to 4pm CEST
That's great news! πŸ₯³ Thank you all!
s
Especially if they're confident of the expertise of their code-reading audience.
I guess it's a trade-off between more low-level vs external library... More flexibility vs less error-prone. Thread/Executor vs ReactiveX/IO
Hey @julian, I thought of another API that might be more interesting than
parTupled
when I was writing new documentation and working on the KotlinConf workshop. Super curious what you think about this API. If you're interested on working on this I'd be super happy to help and guide you ☺️ https://github.com/arrow-kt/arrow/pull/2957
j
Thanks for the opportunity @simon.vergauwen. I gladly accept. 😊