arrow icon indicating copy to clipboard operation
arrow copied to clipboard

`Ior.handleErrorWith()` f parameter lacks a Right value

Open theycome opened this issue 5 months ago • 11 comments

f cannot generate Ior.Right or Ior.Both since it is only passed a leftValue Needs to be f: (A, B) -> Ior<D, B>

https://github.com/arrow-kt/arrow/blob/8c27809a20c7e8433faf0e844c9c38d00aff9a85/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Ior.kt#L428-L436

theycome avatar Jul 11 '25 22:07 theycome

But it's meant to handle Left too, so at the very least it'd be something like (A, B?) -> Ior<D, B> no?

kyay10 avatar Jul 11 '25 23:07 kyay10

Idk.. yes its meant to handle Lefts, but.. at the same time it can generate Rights and for that it needs the inital Right, nullable seems to serve no purpose here - f implementation can just ignore the parameter if it`s not needed

fun f(l, r) {
  return Ior.Left(l) // whatever

theycome avatar Jul 11 '25 23:07 theycome

It doesn't need to generate the right from the initial right though. The type R may be known to the caller, so e.g. something like foo.handleErrorWith(...) { Ior.Right(42) }

kyay10 avatar Jul 11 '25 23:07 kyay10

yep, makes sense

theycome avatar Jul 11 '25 23:07 theycome

I mean, I think we can safely add such an overload (overlord resolution will take care of it). I'm tryna think of what the signature of f would be though. Any suggestions? (A, B?) -> Ior<D, B> likely isn't good enough. (A, Option<B>) -> Ior<D, B> maybe?

kyay10 avatar Jul 12 '25 00:07 kyay10

I don't wanna shut this out completely since maybe it's useful for someone. I think Ior doesn't receive as much love as others and maybe it should. If you're happy though with the current state of affairs then that's good!

kyay10 avatar Jul 12 '25 00:07 kyay10

Ahh.. I missed this point - we call f from two branches. When we are in Left one we dont have initial right value so calling f(leftValue) is ok, but when we are in Both we do have right value so the client might expect receiving f(leftValue, rightValue). So logicaly we have here two call variants, So probably (A, B?) -> Ior<D, B> is a nice fit to indicate with null that we don`t have initial right value.

theycome avatar Jul 12 '25 12:07 theycome

Also to note the inconsistency in Both branch - where we produce Boths for Left & Both cases, but in Right case stangely enough we simply produce Right. As I understood the logic for this branch, we enhance type transformed by f with stuffing initial Left/Right in there. So probably end result should have been Both for all branches?

And final thought: why do we need combine functor at all? If we would have fed initial right & left into f the client could transform them any way he liked, combining and what not. Why overload the interface?

theycome avatar Jul 12 '25 12:07 theycome

public inline fun <A, B, D> Ior<A, B>.handleErrorWith(f: (A, B?) -> Ior<D, B>): Ior<D, B> {
  contract {
    callsInPlace(f, InvocationKind.AT_MOST_ONCE)
  }
  return when (this) {
    is Left -> f(value, null)
    is Right -> this
    is Both -> f(leftValue, rightValue)
  }
}

Seems interesting 🤔

kyay10 avatar Jul 13 '25 17:07 kyay10

val ior = ...
ior.leftOrNull?.let { left ->
  val right = ior.rightOrNull
}

Gives you the same type of info, but without needing to stay within Ior.

kyay10 avatar Jul 13 '25 17:07 kyay10

public inline fun <A, B, D> Ior<A, B>.handleErrorWith(f: (A, B?) -> Ior<D, B>): Ior<D, B> {
  contract {
    callsInPlace(f, InvocationKind.AT_MOST_ONCE)
  }
  return when (this) {
    is Left -> f(value, null)
    is Right -> this
    is Both -> f(leftValue, rightValue)
  }
}

Seems interesting 🤔

I agree

theycome avatar Jul 15 '25 14:07 theycome