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.