Short description of the issue:
Except using Driver, current RxSwift design does not allow to define and consume error-less Observable streams.
Internally Driver relies on Observable and runtime checks, which can fail and error.
Most errors are prevented by making the driver creation feature internal to the framework. Therefore, most of the time the conversion from Observable to Driver is painful and brings lots of unnecessary features (main thread, shared subscription).
A lot of RxSwift Primitive are highly dependent on errors (e.g. binding an Observable to a Subject can make it silently fail). See #1116 for example of typical use cases.
I expect to be able to create chains of observers which will either be able to error or not and consume them accordingly.
Proposition:
I used ReactiveCocoa a lot in the past and my experience tells me that having typed errors is not so enjoyable (flatMapping on Error types while combining streams with operators is painful).
However I Feel that swift typing and generic system would allow us to create Observers, Observables and Events having a specific kind (trait) (Error-enabled or not).
This specific kind would prevent or allow binding at compile time using this logic :
ErrorlessObservable can bind to ErrorObservableErrorlessObservable can bind to ErrorlessObservableErrorObservable can bind to ErrorObservableErrorObservable.flatMap(ErrorlessObservable) -> ErrorObservableErrorObservable.flatMap(ErrorObservable) -> ErrorObservableErrorlessObservable.flatMap(ErrorlessObservable) -> ErrorlessObservableErrorlessObservable.flatMap(ErrorObservable) -> ErrorObservableIn this context, Observer would be a specialization of the ErrorlessObserver (Observer can be cast to ErrorlessObserver)
On the opposite ErrorlessEvent is a specialisation of Event by removing the .error case. (ErrorlessEvent can be cast to Event).
With Observable I'm a bit lost, I don't know understand the full implementation of RxSwift.
I guess we will need two kind, or an associatedType defining the required Observable type. Then we would have operators allowing us to lift the error-less kind to error-enabled or handling errors.
I feel that in most case the operator implementation would need to implement the error-less path except when the operator can fail.
You probably already discussed this matter in the past and I welcome conversation about it. I would also enjoy thinking about it in more detail and work it on my spare time if you feel that we can elaborate a solution which could be included in RxSwift.
RxSwift/RxCocoa/RxBlocking/RxTest version/commit
3.4.0 (latest at this time)
Xcode version:
8.3.2
Installation method: Non Relevant
I have multiple versions of Xcode installed: Non Relevant
Level of RxSwift knowledge:
Hi @jeremiegirault ,
Except using Driver, current RxSwift design does not allow to define and consume error-less Observable streams.
You can define your own traits if you like, including that have guarantees you want, in the same way we've defined Driver, Single, Maybe, Completable and soon Publisher and make them publicly extensible.
Internally Driver relies on Observable and runtime checks, which can fail and error.
We don't rely on any runtime checks.
Therefore, most of the time the conversion from Observable to Driver is painful and brings lots of unnecessary features (main thread, shared subscription).
I think that no matter how you define your observable sequences, you will still have the same issues.
Every concept one defines adds some improvements, but it also introduces some costs. The art is figuring out the sweet spots that bring a lot of improvements and few costs.
I've designed these interfaces according to my experience. They have performed pretty well so far but I'm sure they can probably be improved, and we will continue to improve them.
There were certain patterns I've noticed, usually the only parts of system that can't fail are the ones that only copy state, do simple transformations to data or are used for event propagation.
There might be some other ones ofc, but these were the statistical anomalies I've noticed.
In all of these cases it makes a lot of sense to share that state or events sequences (SharedSequence). Once you have shared state, and you have locks to synchronize that shared state it it waaaaaay safer to perform them on same serial scheduler. Both because of potential wrong assumptions about invariants that programmers usually make, and because of the way all reactive systems use locking.
In that case you are using MainScheduler and main dispatch queue as a more coarse grained lock around your system.
I was a bit worried that Driver would be hard to extend, but I think that hasn't been as big concern as I've thought. From time to time I add some custom operator, and it's perhaps a bit annoying to convert to and from Driver, but it's not so often. We've also added some additional changes that improve that conversion that we'll soon release.
A lot of RxSwift Primitive are highly dependent on errors (e.g. binding an Observable to a Subject can make it silently fail). See #1116 for example of typical use cases.
Right now we have Driver that models state propagation, but we'll also merge #1116 to add event propagation. That will probably be named Publisher, or something similar, and I think @sergdort made a valid suggestion about using Relay as a way to specify event source. We will probably name it Relay.
That work was dependent on #1275. #1275 will be merged and published soon. After that we'll focus on #1116.
Besides event and state propagation, I'm not aware of any other concept that is statistically significant, but can't ever fail. Maybe there exist some.
I expect to be able to create chains of observers which will either be able to error or not and consume them accordingly.
Is there some other use case you need this for beside the ones you've mentioned?
However I Feel that swift typing and generic system would allow us to create Observers, Observables and Events having a specific kind (trait) (Error-enabled or not).
This specific kind would prevent or allow binding at compile time using this logic :
It would, but it would introduce a lot of complexity and confusion when reading the code, and in the long run it would be throwaway work since Swift will probably allow some kind of parametrized error throwing in future, and we have already ways to model most common cases.
I don't see how introducing another errorless set would be different from using generic errors.
If Swift will support parametrized error throwing in future, we will probably find some way to incorporate generic errors.
If that happens nothing significant will change, that will just enable us to have a bit elegant interface between shared sequence and generic observables. If people are using Observables, they would be able to typealias observable in a backwards compatible way, if people are using Driver or Publisher, their code would just continue to work.
If you want to do something like that for yourself, I guess you can. Take a look at what we've done for Single, Maybe and Completable. We are using existing observable sequence implementations, but you also have a way to publicly extend the concepts.
Ok, I will try to create a such a trait on my spare time.
I support what you said and guess it's ok to use Driver in that way.
I think the generic sharing operator will bring more clarity to shared sequence traits, and Driver actually (excepted on a few situation) fit most use cases when needing error-less Observables
Just chiming in to say that I would definitely find it useful to have a trait that can't emit an error, but unlike Driver, doesn't have to be on the main thread or share.
I try to avoid Error in favor of something like a Result enum, in which case it's nice to have a compile-time guarantee that there won't possibly be an Error coming.
Most helpful comment
Just chiming in to say that I would definitely find it useful to have a trait that can't emit an error, but unlike
Driver, doesn't have to be on the main thread or share.I try to avoid
Errorin favor of something like aResultenum, in which case it's nice to have a compile-time guarantee that there won't possibly be anErrorcoming.