https://kotlinlang.org logo
#arrow
Title
# arrow
d

dave08

11/14/2023, 9:46 AM
Wondering if it might be a good idea to add some kind of
parRun { }
in the
Raise
context for something similar to what
parZip
does, that might be a pretty powerful idea... one could always use
async { }
from coroutines, but the handling of errors and bypassing things could maybe be simplified with
parRun { }
...
s

simon.vergauwen

11/14/2023, 2:36 PM
What would
parRun
do in this case? 🤔
d

dave08

11/14/2023, 2:38 PM
It would do the same as in parZip's lambdas (before the one that merges all the results.
and any failure (Left) could cancel all the other parRuns and clean up just like parZip.
It just makes parZip more "imperative"... like Raise does for flatMap on Either...
s

simon.vergauwen

11/14/2023, 2:48 PM
Not entirely sure I got it, do you have a pseudo snippet in mind you could share? Sounds interesting
d

dave08

11/14/2023, 2:50 PM
Copy code
fun getHomeScreenContent() = either {
  val groups = api.getGroups(..).bind()

  groups.map {
    parRun { api.getGroupContent(it.id).bind() }
  }
}
Or:
Copy code
fun getItemDetails() = either {
  val summary = parRun { api.getItemSummary(..).bind() }

  val details = 
    parRun { api.getItemDetails(...).bind() }
  
  Item(summary, details)
}
Very similar to the parZip/Map variants, but just more imperative.
Which could give the option to do certain things on certain conditions
Copy code
fun getItemDetails() = either {
  val summary = parRun { api.getItemSummary(..).bind() }

  val details = if (summary...)
  
    parRun { api.getItemDetails(...).bind() }
  else
...
  
  Item(summary, details)
}
or any other such things, that parZip would be pretty ugly to use for.
s

simon.vergauwen

11/14/2023, 2:56 PM
I think the second snippet is impossible. because how would
details
and
summary
run in parallel here if they immediately return the value so that's.
Copy code
fun getItemDetails() = either {
  parZip(
    { api.getItemSummary(..).bind() },
    { api.getItemDetails(...).bind() }
  ) { summary, details -> Item(summary, details) }
}
The other snippet:
Copy code
fun getHomeScreenContent() = either {
  val groups = api.getGroups(..).bind()

  groups.parMap {
    api.getGroupContent(it.id).bind()
  }
}
d

dave08

11/14/2023, 2:58 PM
Copy code
fun getItemDetails() = either {
  val summary = parRun { api.getItemSummary(..).bind() }

  val details = 
    parRun { api.getItemDetails(...).bind() }
  
  awaitParRun()

  Item(summary, details)
}
?
s

simon.vergauwen

11/14/2023, 2:58 PM
So what would be the type of
summary
and
details
? It would need to be something like
Deferred
d

dave08

11/14/2023, 2:59 PM
Or:
Copy code
fun getItemDetails() = either {
  val summary = parRun { api.getItemSummary(..).bind() }

  val details = 
    parRun { api.getItemDetails(...).bind() }
  
  Item(summary.bind(), details.bind())
}
where the binds on the parRun await their results
That would probably be even more flexible than
awaitParRun()
and Arrow users are already used the notion of
bind()
to get results..
I know the second example I gave above could be done with parMap, but when the logic gets more complex on exactly what to run in parallel while `map`ping, then it becomes more relevant to have another construct that's more imperative...
Or maybe it would be enough just to define a
bind()
on
Deferred
@simon.vergauwen? What do you think of the whole idea?
The possible problem with not having a separate construct like parRun would be when you need to handle neat cancellation when something raises... if you have parRun you can somehow keep track of the Deferred's that are being run (like accumulating their Jobs in some kind of list), so that they can be cancelled. Also, in either { } it would be cleaner not to have to use an extra nesting of coroutineScope { } to start the async's or have to manage a CoroutineScope manually...