RxSwift: Revisit typed errors?
We discussed typed errors here on github a while back. Maybe you’ve discussed on slack as well. I thought maybe it is time to revisit this question.
Unambiguous API The user of your API doesn’t need to make assumptions. She/he will know what errors your API produces. So will your unit tests.
Let the compiler do the job
Type safe languages have the benefit of letting the compiler do work that you normally have to unit test. Does PushHandler
return any other error than PushHandlerError
? No need to unit test mundane stuff like that if the compiler can tell you right away.
Sequences that can’t fail Some sequences can’t fail and that is valuable information when dealing with them as it makes code less engineered and less complex.
~~
RAC has a great writeup on how working with typed errors is like .
Would it be possible to change the API in such way that it is possible to opt-in to typed errors?
I love typed errors, but hey, maybe that’s just me 😃
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Comments: 60 (47 by maintainers)
It looks like Swift 5 introduces
Result<Value, Error: Swift.Error>
.This thing compiles:
but it would be ideal if
would also compile.
As I see it we could introduce something like
and make
I think that if we did that it would be win/win situation because we would maintain backwards compatibility and provide more type safety options.
Once
Swift
adds default generic arguments, we could just make:1rst stage: First provide additional features and type safety together with more dynamic API. 2nd stage: Make a breaking change by effectively changing from:
to
I think we could implement first stage for RxSwift 5.0. When should we make the second stage is hard to tell because there is a lot of legacy and educational code out there. It would be ideal if Swift 5.0 introduced default generic arguments and if we just made it all at once, but it seems like this is not the case.
For the second stage one could make a global rename of
Observable -> DynamicObservable
, so it’s not a horrible breaking change.Anyway, these are my thoughts.
Hi, guys!
I personally agains typed errors! 😃
flatMapError
,mapError
,attemptMap
,NoError
and family)Also I feel like it violates duality math behind Rx
@hfossli that’s a great point in general but I also feel like it’s a “developer concern” and not necessarily a RxSwift concern. The “official” ReactiveX standard is failing on sequence error.
I wouldn’t be opposed to a type of Observable that can’t fail / provides typed errors but I’m not sure it’s exactly in the scope of this library …
Also, some people rely on ReactiveX-based implementations to work the same across platforms so having this “discrete” implementation that is only relevant to RxSwift could be highly confusing and needs to be well thought out IMO.
In 99% of the cases these issues are resolved with using a Result type, which is just a “standard” part of RAC, really. RxSwift gives you the freedom of choosing how you want to model your results and doesn’t “bind” (pun not intended) you into a specific format (for better or worse)
Just me $0.02
I skimmed over this issue quickly as it’s a topic I would like to track and provide feedback. @kzaher is there a reason you want not permit custom types for
Complete
? We don’t support it now but I think it would make sense to extend this possibility for the future. Especially because we don’t needNeverCompletes
as it won’t have any compiler support compared toNever
.If this was the new
Event
enum in RxSwift.It can have strong compile time guarantees for switch exhaustiveness:
If possible I would also follow the precedence of Swift for the naming scheme (you could also argue that we should follow the precedence of Rx, I know).
Here is the rationale for the naming
Result
ended up with:Source
I’m playing with ideas for RxSwift 5.0 here. Didn’t have much time to play with this, but hoping to get benchmarks running soon so we can estimate performance impact.
I’m trying to simplify the project structure while doing this, but there is bunch of code in the project 😃
I’m publishing this just in case anyone is interested in the prototype and has early feedback.
Once benchmarks are running, we’ll estimate the performance impact, optimize for the benchmarks and then port the rest of the project once we’ve profiled it.
At least that’s the plan.
Hi guys,
It has been a long time since I’ve responded here. Releasing 4.4.1 was a priority. I’m hoping that 4.4.1 is the last patch we need to release for 4.x version and that the next version will be directly 5.0. I guess this is the only major change for 5.x.
The plan for 5.x would be to first create a 5.x branch and create a prototype of generic
Observable<Value> = ObservableSource<Value, (), Swift.Error>
.I’ll try to do that this or next week, but can’t make any promises. After that I guess it’s a matter of porting all of the operators.
@nikita-leonov I don’t think this limits it to just
NeverCompletes
orCompletes
.If we are doing this, then we are doing this right.
Completed
event is no less important than any other.Complete
communicates does the state machine have completed state. Is the state machine finite.I actually mostly use
All other combinations are rare IMHO.
I guess you can do
Observable<Never, Void, Error>
orObservable<Never, Void, Never>
depending on your use case. Define it as you wish.Yes, we would generalize all of them, not just
Observable
.I don’t think ignoring
Completed
makes sense. You will need to typealiasObservable
s anyway if you want to make your code sane. The most compelling purpose of introducingError
parameter is to be able to communicate that the state machine can never enter final error state. Final complete state is almost as bad if you want to play the type safety card. Read my posts above for rationale.I don’t think this sentence characterizes the spirit of this project correctly and there isn’t a parallel between errors from RxSwift and ReactiveSwift.
Both RxSwift and ReactiveSwift have something named error in their events, but they aren’t conceptually interchangeable.
You would use error in RxSwift as a short switch:
It is not recommended to encode any business logic in errors, catch them and then parse the error information. This is anti-pattern IMHO.
I’m usually using resultish values as sequence elements because you want the compiler to notify you if you’ve forgotten to handle some case.
There are some type safe properties that this project offers that some other projects don’t:
ReactiveSwift could also advocate that their Signal, Properties, etc… are the latest and coolest things ever.
Sure 😆 🥂 🥂 🥂
There is just one tiny detail nobody is mentioning, so I’m going to mention it anyway. Maybe somebody will find this information useful.
There are actually 3 events in Observable (or 4 in RXS/SignalProducer) and for some reason everybody is forgetting about Completable event (or Completable/Interrupted in RXS case).
I can’t be entirely sure why people are advocating for parameterized errors, but I’m assuming that they are probably advocating this because they want to be sure that they’ve handled all of their errors, and their user interface won’t die because they’ve forgot to handle an error (that’s one of the reasons why we have Driver/Signal in RxCocoa).
BUT
Even if we parameterized Error type, that still wouldn’t be enough to guarantee that your user interface won’t die. Your sequence can Complete and you also need a guard against that.
This is not just theoretical concern, consider this code:
Hint: If you’ve found this comment, more useful approach is maybe retry family of operators but be careful with them, because you could accidentally overwhelm your service and create a DDOS attack on your service if you don’t properly design a retry strategy, so try to implement some binary back-off retry strategy to prevent these kinds of issues.
This is perfectly safe to bind to user interface, but it doesn’t prove compile time that your code is correct just because it has
NoError
. You UI will still die even if no error is thrown. Complete event is also a silent killer.I’m not trying to say that reminding the user that somebody hasn’t handled the error properly isn’t a nice handy thing (I find this really useful with Driver/Signal from RxCocoa), but that it doesn’t provide you with guarantees that one would assume from the code itself.
The case I’ve presented isn’t theoretical. I’ve been watching people day after day people writing code like this (usually with Driver, but nothing in the interface would prevent people from doing the same thing with Observable if we added typed errors). If you are thinking the problem was people, I’m not that convinced, these were very capable people IMHO that I respect, but they just needed time like everyone else.
One could think, ok, let’s add typed complete event.
This again won’t work. One can just replace
empty
withnever
and you are back to square one. Your program type checked, your sequence will never complete, but your user interface is dead again because it won’t ever receive any elements.If errors regarding
.catchError(x => Observable.empty())
are common, it is reasonable to assume that errors regarding.catchError(x => Observable.never())
would be common somewhere approx same order of magnitude.But yes, we don’t have NoError type in Observable, sorry ¯\_(ツ)_/¯
Also in my opinion
Observable
based API are perfect example ofOpen Closed Principle
, looking on API like:The data may come from cache, network, streaming connection or both. It allows to model you app without worrying that changes of platform will break something. Adding typed error will most likely break the client if you change type of the error or add another
case
to yourError
enumAlso if I’m allowed to bikeshed here a little, I can imagine that we could have two event types.
If you look closer here you realize that we just added support for
Completion
asVoid
orNever
. In fact we can go even one step further, asEvent
is simply the same asResult
. The Swift community will likely extend the type with a default for the generic typeFailure
toResult<Success, Failure: Error = Error>
, which is the exactly what we want here as well.I can imagine (aside from slightly different names) that RxSwift could adopt such
Result
type as itsEvent
type, and provide a wrapper to restore completeness.What do you think?
I prefer error as well. It represents the
Swift.Error
type anyways. I was mainly asking about the generic letter you assigned to it (F
). I imagine you did this becauseE
is for the element type, in which case it might make more sense to doError: Swift.Error
instead ofF: Swift.Error
. But alas, I don’t mind that much 😃To me it seems to have typed error https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md or maybe i am missing something.
The declaration is
Which allows to define the type, right?
This thread has some really interesting insights, but - it is 3 years old and Swift’s default generics isn’t around the corner. This could likely be a RxSwift 7 feature is Swift’s features progress accordingly. When the time comes we can revisit this thread or open a new one that links to this one.
Thanks to everyone who participated and for the great ideas!
About completion subtype, that could basically be fine, I think. But it would be something that needs to be investigated for RxSwift 6, probably.
Starting from the end - I meant “conform” in the actual meaning of the word (e.g. “shoehorn” the event type into a result even though it doesn’t represent the same thing), not the technical sense of protocol conformance.
About the rest - I respect your opinion but I personally highly disagree. I think you are misinterpreting what this project is about. It’s not about reinventing Reactive Programming. It’s about providing the ReactiveX standard in a great Swift-centric API that follows the same standard across all of its platforms, which is one of the biggest promises of Rx in general.
RxSwift’s purpose isn’t to steer away from Rx (unless we’re talking about some operators or method names, but definitely not core concepts), but to provide this Swift-centric implementation that is understandable, stable and expectable. This is different than ReactiveSwift, for example, in the sense they’re making their own API without regards to Rx specifically or any other standard (for better or worse).
What you call a “historical quirk” is basically a standard. It’s like you’ll make a browser that decides HTML is outdated and would decide to make its own markup language. It might be “nice”, but it’s not practical or a good move.
In general, I personally find the
next
,error
,completed
standard very simple and basically it just “makes sense”, to me.This entire post is my personal opinion. I don’t represent Krunoslav’s vision or anyone else’s, but this is how I teach Rx to people (it’s a platform-agnostic standard), and the thoughts you share here are a large deviation from that IMO.
You could use a closed class hierarchy instead of a protocol.
RxSwift should use default Swift error mechanism since the library is meant for Swift language users.
If Swift team decides to ship a built in Result type and proclaims that this is the default error handling mechanism, and if Apple is planning to migrate all of their existing APIs to conform to their new Result type, then yes, we will also migrate towards it.
What do you mean by this? Links?
I’m not sure what do you mean by this.
When I say parametrized typed error throwing, I mean something like this.
I was a bit out of the loop, so maybe I don’t have the latest information. Is this coming?
About opting in to typed errors, would that maybe require Swift being able to have default values for parameter type information? I don’t know if that exists, is a good idea or anything – just popped in to my mind… E.g.
class Observable<Element, ErrorType = Error>
so that it is possible to both writeObservable<String, MyError>
andObservable<String>
?Or is there other ways of opting in?