-
Notifications
You must be signed in to change notification settings - Fork 451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Remove top-level fold in favor of recover et al #3309
Conversation
@@ -34,7 +33,8 @@ public suspend fun <Error, A, B> Effect<Error, A>.fold( | |||
callsInPlace(recover, AT_MOST_ONCE) | |||
callsInPlace(transform, AT_MOST_ONCE) | |||
} | |||
return fold({ invoke() }, { catch(it) }, { recover(it) }, { transform(it) }) | |||
val a = recover<Error, A>({ invoke(this) }, { return recover(it) }, { return catch(it) }) | |||
return catch({ transform(a) }) { catch(it) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dislike that this is needed. You'd expect that all we need is the one-liner:
recover({ transform(invoke()) }, { recover(it) }, { catch(it) })
but sadly this has slightly different behaviour when transform
invokes a captured raise
call that was captured in block
. This is because fold called Raise.complete
before it executed the transform
, while the one-liner calls transform before Raise.complete
, thus resulting in a captured raise that is immediately invoked. This violates the test EffectSpec.shiftLeakedResultsInRaiseLeakException
which seems to suggest that this behavior is intentional. Thus, we have to use non-local returns to get rid of all our error cases, and then in our happy path we execute the transform
, keeping in mind that it can throw
I don’t think we should merge this. Apart from breaking compatibility, this would break the expectation of what a |
My question is though, is It's likely a matter of personal taste, but when looking at the places in Arrow's codebase that changed from this PR, most places got clearer in their intent to handle errors. In fact, they look a lot more like what an imperative Take for example this change: fold<NonEmptyList<Error>, B, Unit>(
{ transform(RaiseAccumulate(this), item) },
{ errors -> error.addAll(errors) },
{ results.add(it) }
) to recover(
{ results.add(transform(RaiseAccumulate(this), item)) },
{ errors -> error.addAll(errors) }
) The former separates the operation from the transformation. It's rather unnatural considering that the operation is a lambda right there that can be modified to include the transformation easily. The latter does the operation and transformation in one go, and only afterwards does it handle errors. For I think that the rest of the Raise-related operations use a mental model of imperative code with typed error handling, and hence |
After some discussion, we've decided that changing
Whereas (@kyay10 thanks for raising this issue, making concrete how such a reinterpretation of the API would look like) |
I've been thinking about the need for a
transform
lambda for top-levelfold
for raise. I understand that it makes sense for there to be atransform
in the happy path if we're thinking of aRaise<E>.() -> A
as anEffect
, i.e as a proper data type, but I think the mental model surroundingRaise
seems to be more around error handlers than it is about representing a data type that can succeed and fail. I think looking at all the raise builders other than fold makes that very clear, because none of them take any transformation parameter for the happy path. That's because a transformation is just a last step of the calculation inside ourRaise<E>.() -> A
block, so we can always very idiomatically do such a transformation inside the block with no cost. I see howEffect.fold
is very idiomatic though, and it makes since for it to have atransform
When removing the
transform
lambda, it made it visible that perhaps top-level fold shouldn't exist, instead being replaced by recover, which matches the mental model of raise providing error handling strategies. This PR does that, and perhaps one can see how some of Arrow's own code becomes clearer. I understand however that maybe thefold
ship has already sailed!