-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Observable<Void> Usage or VoidObservable #3037
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
Comments
I see two problems: Type inference limitationsThe lack of cast-down type-inference in the Java language. For example: if I have an interface IObservable<T> {
<U, R super T & U> IObservable<R> mergeWith(IObservable<U> other);
} Here, super is not allowed and gives a compiler error. If I try to cheat this out via interface IObservable<T extends R, R> {
void subscribe(Subscriber<R> child);
default <U extends R> IObservable<R, R> mergeWith(IObservable<U, R> other) {
return null;
}
} This requires me to specify some common basetype so the merge can fall back onto it, however, it has limitations too: IObservable<Double, Number> io1 = s -> {};
IObservable<Integer, Number> io2 = s -> {};
IObservable<Integer, Object> io3 = s -> {};
io1.mergeWith(io2);
io2.mergeWith(io1);
io1.mergeWith(io3); // won't compile, incompatible types. The only thing that works is the static Observable<? extends Number> merge = Observable.merge(o1, o2); But still, it forces the user to have Operator methods are finalEven if you have your own, fixed type observable, the following won't compile because our operator methods are final: public static final class VoidObservable extends Observable<Void> {
protected VoidObservable(OnSubscribe<Void> onSubscribe) {
super(onSubscribe);
}
public <T> Observable<T> mergeWith(Observable<T> other) {
return null;
}
} The compiler will most likely complain about same-erasure non-overriding methods or just simply about override attempt of a final method. You'd have to name this method something else and remember to use it instead of the standard. |
I took a crack at changing RxNetty to use |
@akarnokd @abersnaze Thanks for your comments, let me answer them one at a time.
You are absolutely correct in identifying these issues & I should have clarified my intent better. What I was intending to is to introduce a class like: public class VoidObservable extends Observable<Void> {
protected VoidObservable(OnSubscribe<Void> f) {
super(f);
}
public <T> Observable<T> continueWith(Observable<T> next) {
final Observable<T> cast = unsafeCast();
return cast.concatWith(next);
}
public <T> Observable<T> mergeErrorWith(Observable<T> next) {
final Observable<T> cast = unsafeCast();
return cast.mergeWith(next);
}
private <T> Observable<T> unsafeCast() {
@SuppressWarnings("rawtypes")
final Observable rawSource = this;
@SuppressWarnings("unchecked")
final Observable<T> cast = rawSource;
return cast;
}
} and add any other methods specific to Do you see this working or making sense?
Can you elaborate more i.e. were you trying to make RxNetty's |
The observer of the return value from |
@abersnaze I agree with you on why Observable<?> source = Observable.just("Hi");
source.concatWith(Observable.just(1)); The above isn't valid as Observable<?> source = Observable.just("Hi");
source.cast(Integer.class).concatWith(Observable.just(1)); which is the same thing as |
You wouldn't use concat because it doesn't make sense. Concat is concatenating the data you don't care about the data you are only using concat because enforces an order and propagation of errors. Here is code that does what you want.
|
Sure, I think the point here is whether using Now, if I take out |
I don't think I think something like Observable<Void> someStream ...
Observable<T> otherStream ...
Observable<T> combined = someStream.continueWith(otherStream); This achieves the goal of ignoring output (a 'Void' or |
I see, so what you are saying is add The only ambiguity will be, what if, someone uses it with a non-void I am fine with either approaches, if and when we have a consensus here, I can submit a PR for this change! |
Yes, it would behave like |
I'm okay with that restriction because if someone wanted to use the |
As discussed in issue ReactiveX#3037, the primary use of these operators is to be applied to `Observable<Void>` so that they can be merged and concatenated with an Observable of a different type. Both these operators raise an error if the source Observable emits any item.
Based on hallway discussions about this, we considered renaming for clarity to Do you still prefer |
I updated the code In the PR to have the methods as concatEmptyWith and mergeEmptyWith but did not update the title of the PR. |
After wrestling with the issue of how to convert return asyncBucket
.remove(doc) // emits Observable<JsonDocument>
.compose(new Transformer<JsonDocument, Void>() {
@Override
public Observable<Void> call(Observable<JsonDocument> removed) {
return Observable.empty();
}
}); |
Well, @tonypiazza, it seems to me that if your |
@tonypiazza, I believe @roman-mazur is right. By adding the concatEmptyWith and mergeEmptyWith to Observable we'll give the operators that RxNetty needs to allow @tonypiazza to return |
@roman-mazur and @abersnaze Thanks for your replies. I stand corrected. I tested my earlier solution and discovered that the document was not actually getting removed. By changing the code to use concatMap as suggested by @roman-mazur, it now works as expected. Here is the updated code that works: return asyncBucket
.remove(doc) // emits Observable<JsonDocument>
.concatMap(new Func1<JsonDocument, Void>() {
@Override
public Observable<Void> call(Observable<JsonDocument> removed) {
return Observable.empty();
}
}); So am I correct that the introduction of concatEmptyWith and mergeEmptyWith would allow me to do the following instead? return asyncBucket
.remove(doc)
.concatEmptyWith(Observable.<Void>empty()); Let me know if my understanding is incorrect. Thanks to everyone for a very helpful discussion. |
More like |
@abersnaze The problem with ignoreElements() is that it doesn't change the return type. I want to return |
As discussed in issue ReactiveX#3037, the primary use of these operators is to be applied to `Observable<Void>` so that they can be merged and concatenated with an Observable of a different type. Both these operators raise an error if the source Observable emits any item. Review comments
These new operators are intended to be used on an empty If you want to change the type of an existing |
As discussed in issue ReactiveX#3037, the primary use of these operators is to be applied to `Observable<Void>` so that they can be merged and concatenated with an Observable of a different type. Both these operators raise an error if the source Observable emits any item.
As discussed in issue ReactiveX#3037, the primary use of these operators is to be applied to `Observable<Void>` so that they can be merged and concatenated with an Observable of a different type. Both these operators raise an error if the source Observable emits any item.
What do you think about this names? (I prefer not to force emptiness as commented here #3430) then similar to promises 'then', second merged after first completed connection.write(0x1234).cast(String.class).concatWith(connection.getInput())
connection.write(0x1234).then(connection.getInput())
<V> Observable<V> then(Observable<V>> v1) { return ((Observable<V>) ignoreElements()).concatWith(v1); } flat as flatMap but ignore elements, so nothing to map. connection.write(0x1234).cast(String.class).mergeWith(connection.getInput())
connection.write(0x1234).flat(connection.getInput())
<V> Observable<V> flat(Observable<V> v1) { return ((Observable<V>) ignoreElements()).mergeWith(v1); } none as single but assert emptiness
|
Not sure what you mean by that. Concat has an internal buffer for two Observables but only subscribes to one of them. It requests 2 Observables from upstream and once the first completed, it requests another and also subscribes to the second Observable. In theory, this should establish a pipeline of Observables where there is one ready as soon as the previous one completes. |
As mentioned in the PR, IMO, |
While working on RxNetty, a networking library that layers RxJava on top of netty, I have come across a common case of expressing result of an operation as an
Observable<Void>
. A typical case being the result of awrite
on aConnection
. The API for write looks like:public Observable<Void> write(Observable<T> msgs);
The return value just represents the asynchronous result of the write and hence does not emit any item, so has the type as
Void
.This works pretty well, however, it becomes awkward when we start doing a bit more complex operations over this write. eg: If I am writing a TCP client that sends a message on the connection and then reads the response from the server, I start doing things like:
(Actual example here)
In this example,
connection.getInput()
returns anObservable<T>
emitting items as read on the connection.and in case, I do not want to wait for write to complete, the code looks like:
What is awkward in the above example, is the
cast(String.class)
. It is completely safe as thewrite
never emits an item (Void
) so I can merge/concat heterogeneous streams but it is just awkward.My motivation behind creating this issue is to first see if other folks have come across the same scenario, if so, how have they handled it. Secondly, if this does sound like something worth spending time to make it better, I would like to propose some abstraction on the lines of a
VoidObservable
(for the lack of a better name) which basically provides better constructs around:write().cast().concatWith()
example above, where it is expected to continue with another stream of a different type (String
in the above example)write().cast().mergeWith()
example above, where it is expected to eagerly subscribe to the secondObservable
but listen only for errors from theObservable<Void>
The text was updated successfully, but these errors were encountered: