Sam Pengilly
06/20/2024, 12:35 PMaccumulate()
function to IorRaise
a while back. I'm wondering if now it would make sense to have variants of mapOrAccumulate
and other related functions that operate on an IorRaise
along side the ones that operate on Raise
. When used in the context of an IorRaise
their behaviour would be to accumulate errors and mapped items into a Both
case, like so:
val nums = listOf(1, 2, 3, 4, 5, 6)
val func: RaiseAccumulate<String>.(Int) -> Int = {
if (it > 4) raise("$it") else it * 10
}
either<String, Unit> {
val mapped = iorNel {
mapOrAccumulate(nums, func)
}
val eitherMapped = nums.mapOrAccumulate { func(it) }
println(mapped) // prints Ior.Both(NonEmptyList(5, 6), [10, 20, 30, 40])
println(eitherMapped) // prints Either.Left(NonEmptyList(5, 6))
}
Is this something that has been considered at all? Is it desirable, or would it cause unintended issues or confusion?
My main train of thought with this is working predominantly inside normal Raise
scopes inside of an either {}
DSL, but wanting to encapsulate a small part of my logic in an IorRaise
scope using a nested iorNel {}
, in which I can run something like mapOrAccumulate
to map a list of data, collecting both the errors and the successfully mapped items.simon.vergauwen
06/20/2024, 12:52 PMIs this something that has been considered at all? Is it desirable, or would it cause unintended issues or confusion?The idea has been there, but it was never explored. At this point I'm not sure what any unintended issues could be. This was one of the ideas I had for
Raise
. Afaik you should even be able to refactor func
to just Raise
and it should still work.Sam Pengilly
06/20/2024, 9:02 PMSam Pengilly
06/20/2024, 9:03 PMsimon.vergauwen
06/21/2024, 6:21 AMSam Pengilly
06/21/2024, 10:32 AMRaiseAccumulate
and swapped the Raise
receiver for IorRaise
. There's some interesting challenges here and there which will definitely need context parameters to solve nicely. As an example, I can't simply duplicate the mapOrAccumulate
function that takes an Iterable
receiver and returns an Either
, it would need to be moved into the Raise
class, and a duplicate would have to go into the IorRaise
class.
Similarly, when using mapOrAccumulate
from inside an ior(combineError = ...) { ... }
builder, the combine function can't be accessed from the extension, so to make a version of mapOrAccumulate
work without a combine parameter, it has to be moved into the IorRaise
class directly.
I've ended up with this quick example case, building upon my earlier one which seems to flow nicely in the different use cases. But there is potential for some confusion. This may just be because I was still filling out functions, but before I realised that combineError
wasn't being inferred, I had exactly the same code as this which all compiled, but the mapOrAccumulate
in the ior(...) {}
builder was actually implicitly falling through to the parent either {}
builder because there was no matching overload in IorRaise<Error>
. It's resolved simply by making sure the overload exists on IorRaise
for that case (by creating one directly in the class as mentioned above). But it does show an area where if all cases aren't covered, unexpected behaviour will occur.