-
Notifications
You must be signed in to change notification settings - Fork 28
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
RemoteData Applicative ordering #26
Comments
Thanks for the question. Well I think that if we have a list of RemoteDatas and at least one of them hasn't been loaded yet (resolved) then we should treat the whole list as RemotePending because we can't say wether everything is RemoteSuccess ot RemoteFailure. The same applies for RemoteInitial (we don't have anything at all) so I'd say this is by design. |
I suppose my reasoning is that, if we have a list of Successes and Failures, we would obviously show it as a failure. So, if we have a list of Successes, Failures and Pending, no matter what the outcome of that Pending, it will ultimately resolve to Failure anyway. In practice, when we have a list of RemoteDatas that is meant to be represented as a single unit, once one fails, we want to display that to a user, since there's no way to recover without transitioning the Failure. |
@raveclassic I think I agree with @quicksnap 's reasoning here, but it depends on what we want the fail-fast case to be, right? When thinking about sequencing/traversing Options the fail-fast case is None. When thinking about sequencing/traversing Eithers the fail-fast case is Left. So, both of these treat the "left" or "failure" side of the branch as the fail-fast case. It seems like your requirement for sequence/traverse returning a Failure is that all items are Failures, but is that really the desired outcome? I mean, yes, having all Failures should result in a Failure overall, but, really, if we are grouping RemoteDatas together & then using traverse/sequence, I think the expectation would be that even a single Failure being present would mean that we have to treat the overall operation as a Failure, right? Like, if we have both a Pending and a Failure, we don't know yet whether or not the Pending item will succeed or fail, but we do know that something else has already failed. If we need both of those things to Succeed, this should be counted as an overall Failure, because it no longer matters what happens to the currently Pending item.... Or, at least, that's how I'm thinking about RemoteData right now... but I guess it's somewhat open to interpretation. |
I thought it might be a good idea to check other implementations to see what they do. If we look at the |
To recap the reasoning: once a failure has arrived, the outcome of that entire sequence is forever Failure so long as one exists, so any pending/initials are inconsequential to a sequence. |
Yeah I see it makes sense. So what should be the correct priority? |
/cc @sutarmin |
Makes sense for me too, nothing to add |
@quicksnap So if you're ok with the priority would you like to open PR? |
@raveclassic IMO the current implementation is ok, its behaviour is consistent with the following:
|
@gcanti Well well.. I'm not so strong in maths but why should it be isomorphic? |
RemoteData<L, A> = 1 + 1 + L + A import { failure, initial, pending, RemoteData, success } from '@devexperts/remote-data-ts'
import { Either, left, right } from 'fp-ts/lib/Either'
import { none, Option, some } from 'fp-ts/lib/Option'
import { Iso } from 'monocle-ts'
const getIso = <L, A>(): Iso<RemoteData<L, A>, Option<Option<Either<L, A>>>> =>
new Iso(
s => s.fold(none, some(none), l => some(some(left<L, A>(l))), a => some(some(right<L, A>(a)))),
a => a.fold(initial, oe => oe.fold(pending, e => e.fold<RemoteData<L, A>>(failure, success)))
) If you execute const datas: Array<RemoteData<string, number>> = [initial, failure('foo'), success(1)] what's the result? sequence([failure('bar'), failure('foo'), success(1)]) = failure('bar') // != failure('foo') p.s. |
@quicksnap @joshburgess What do you think? |
Hmmm, I guess that does make sense, but I think that, from a performance/usability perspective, failing-fast on any combination including a Failure seems like the more useful implementation. I mean, it seems like the functionality that we'd want most of the time. Why wait on currently in flight requests if we already have a Failure? Or... why fire off NotAsked, aka Initial, requests if we already have a failure? Then again, maybe we do still want to make those requests anyway. I guess it depends on what we intend to do after this point. Like, would there be any retry logic for Failures, etc.? I'm not really sure what the answer is, but I'd think we'd want to treat the operation as a Failure as soon as we encounter one in the UI just so that we aren't waiting around on subsequent requests. |
@joshburgess I thought absolutely the same way. But logic like canceling requests is not about
The user has the first error, then somehow it changes to the other one. I find this behavior pretty weird. |
@sutarmin in the use case of |
@gcanti Does this isomorphism make sense for the other side of the discussion? const getIso2 = <L, A>(): Iso<RemoteData<L, A>, Either<L, Option<Option<A>>>> =>
new Iso(
s => s.fold(right(some(none)), right(none), l => left(l), a => right(some(some(a)))),
a => a.fold<RemoteData<L,A>>(failure, r => r.fold(pending, oe => oe.fold(initial, e => success(e))))
) |
@sutarmin I don't think we'd want to show specific failure messages for individual failures in this case (when using sequence/traverse to treat them as a group). I'd probably just add an interpretation step where I convert to a |
@raveclassic What should the action be on this? Do we want to change the existing behavior? I'd be happy to make the changes, but it would be a breaking change somewhat, since the behavior would be slightly different. |
@quicksnap Yeah I'm afraid this's going to be a massive breaking change for us because all our projects rely on the fact that Pending has a higher priority. |
@raveclassic from a consumer standpoint, how would opting in to those different |
We could export several instance constants (not only Also I think it's time to write some laws tests. |
@raveclassic Maybe, we could have two separate versions under different directories? the new version could import and share code that would be the same from the current version. then, we could keep the exact same naming (still calling it Alternatively, the alternate version could be exported from the same file and just be called |
Any thoughts on the above? I could help create a PR as long as I know which approach you'd like to take. |
Something similar is already happening in |
I can't think of a proper name for such a "failing-fast" instance. Maybe |
Maybe, something like People who use it will probably use the |
I created a PR to continue this discussion: #27 |
So we're fine with changing the priority. Feel free to open PR. |
Ok, sounds good. I'll submit another PR soon. |
I ended up tackling all of those things we mentioned in that closed PR all at the same time: changing the I'm almost done. I just need to add tests for the new things now. I should have the PR up within the next day or two. |
New PR open 👍 #28 |
Currently, the applicative priority of RemoteData is
initial > (pending, failure, success)
. Example:It makes more sense that any
failure
should result infailure
. Then, anypending
, theninitial
, and finallysuccess
.Thoughts?
The text was updated successfully, but these errors were encountered: