How would I handle a list that's mapped results of...
# arrow
d
How would I handle a list that's mapped results of an exterior service (which could have faults so returns either), getting the valid entries into one list while aggregating the failures to log them at an outer level?
d
That could work... but I was thinking more to
bind()
to the happy path and have an aggregation of the failures to pass down or raise.
p
Copy code
context(Raise<List<L>>)
fun <L, R> Iterable<Either<L, R>>.bind() =
    separateEither().let { (errors, success) -> 
        if(errors.isNotEmpty()) raise(errors) else success 
    }
possibly simpler still would be to use
mapOrAccumulate(myListOfEithers) { it.bind() }
d
That's nice! But unfortunately my service doesn't compile when I use context recievers.. and in this particular case, I still consider the entries that succeed even though some failed, but I want to log them at WARN level on a higher level than the use case level.
Maybe this is an IOR case? (not that I understand anything about it yet...)
I want my success path to read as sequential as possible, while still receiving Left to log it. Only if some worse errors happen, I'd then want to switch to only having a Left and fail the whole process...
p
The design behind
raise
is that it will interrupt the flow (ala railway-oriented programming) which is why
raise
returns
Nothing
. I think in this case you'll need to stick with
separateEither
, process your rights and then manually propagate your lefts back to the outer layer (or provide a callback down into your use-case)
d
Is there a way to do this:
Copy code
val validResults = someIterable.map { service(it) }.onLeft(::callback).bind()
Where
bind()
would only give the success values (w/o having to actually creating two lists)
p
it depends what you want the final bind to do on all values being failure, you could do something like
Copy code
val validResults = someIterable.mapNotNull { service(it).onLeft(::callback).getOrNull() }
if you're happy with all failures resulting in an empty list
I'm not sure if
mapNotLeft
has much benefit over
mapNotNull { it.getOrNull() }
or even
._filterIsInstance_<Either.Right<T>>()
d
Well, in the case of a real fatal error -- it would just return null, which I guess even my idea doesn't work...
I have some error types that are recoverable, and just need to be passed down for logging, whereas others might need to short-circuit...
So really it would be
mapNotLeftOrAccumulate<ServiceError> { service(it).onLeft(::loggingCallback) }
where
ServiceError
is the fatal type
I thought the Validation system works like this, separating the two types, but I'm very new to all this...
p
You'd likely want to explicitly
raise
or
callback
your errors when checking the result, letting fatal errors get caught by the
mapOrAccumulate
and cause the
.bind()
to raise, and treat non-fatal errors as an alternative result and filter if required. Something like
mapOrAccumulate { service(it).recover { if(it.isFatal) raise(it) else null.also { callback(it) } } }.bind().filterNotNull()
(that likely won't work as-is and is missing a bind or something, I'm sure)
validation semantics are effectively "if everything is good, do this with the values, otherwise accumulate and raise all the errors"
d
"if everything is good, do this with the values, otherwise accumulate and raise all the errors"
Oh... so there's no variant of that that wouldn't raise but rather just call a callback... I guess that solution it the way to go then, thanks! I just wonder if I'm not the only one out there who would want some better support for this kind of behaviour in Arrow itself...?
And Ior doesn't help here? Getting both left and right if non-critical?
p
I'll be honest I've never really had a usecase for or played with
Ior
so can't speak to the semantics of it.
I think the approach I've outlined matches your usecase nicely, in that you have a service call, for which you want to treat a subset of failures as "ok" (but possibly tap/log them), and accumulate all "ok" results (including those with no result due to an ignored error, which you can ignore/filter as required)
whether you use
mapOrAccumulate
or just
map
will depend on if you want the first critical failure, or all critical failures
(
mapOrAccumulate
is the new validation capability in Arrow 2.x and 1.2.x 😉)
s
(
mapOrAccumulate
& co are already available on the latest alpha and are planned to be released in 1.2.x)
d
Just the overhead of having to recreate the lists would have been nice to be avoidable... so at least
mapNotNullOrAccumulate { }
might have been useful for that, but you're right, what you posted DOES cover my use case! I'm using the latest alpha 😃...
p
careful - Arrow is a drug 😉