-
Notifications
You must be signed in to change notification settings - Fork 7.6k
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
Adding super/extends so that Observable is covariant #331
Conversation
…, removing a bit of unused code
…ms, as long as Observable remains invariant
RxJava-pull-requests #213 FAILURE |
I'll have to spend some time later playing with this. Anyone else able to try this out and comment on the changes? @jmhofer Can you provide examples or use cases of using the updated code that demonstrates covariant/contravariant usage that couldn't be achieved before? |
Ok, here's an example of what you can do now with the improved import rx.Observable;
import rx.util.functions.Func2;
class Media {}
class Movie extends Media {}
class HorrorMovie extends Movie {}
class Rating {}
class CoolRating extends Rating {}
class Result {}
class ExtendedResult extends Result {}
public class Covariance {
public static void main(String[] args) {
Observable<HorrorMovie> horrors = Observable.from(new HorrorMovie());
Observable<CoolRating> ratings = Observable.from(new CoolRating());
Func2<Media, Rating, ExtendedResult> combine = new Func2<Media, Rating, ExtendedResult>() {
@Override public ExtendedResult call(Media m, Rating r) {
return null;
}
};
Observable.zip(horrors, ratings, combine);
// 0.11: The method zip(Observable<T0>, Observable<T1>, Func2<T0,T1,R>)
// in the type Observable is not applicable for the arguments
// (Observable<HorrorMovie>, Observable<CoolRating>, Func2<Media,Rating,ExtendedResult>)
//
// but works in super-extends branch
}
} |
Lots more |
…cess with them. - only testing zip operator at this time
I can't get through release process to maven central on 2.10.2 for some reason so pinning until that is solved.
Changed order of generics on zip (and combineLatest) to match the rest of the project. Added arties 5, 6, 7, 8, 9 to zip operator. ReactiveX#333 Order of Generics on Zip Operator ReactiveX#103 Add more arities to zip operator
Zip and CombineLatest Operators: Generic Order and More Arities
RxJava-pull-requests #216 FAILURE |
Let me know when you're ready for this to be merged and released. I plan on releasing this as 0.12.0 as it does have some breaking changes. |
That CloudBees build failure is legit, I also can't build
|
In this line: Observable<String> observable = Observable.create(new Func1<Observer<? super String>, Subscription>() is it the case that Java programmers creating an |
…wo more reduceFunctions to zipFunction
@benjchristensen I just noticed that, too, and adapted the @daveray Looks like it, yes. I couldn't make the compiler happy without it, but maybe I'm missing something. If you find something that avoids this, then please let me know. |
RxJava-pull-requests #217 SUCCESS |
Still to do: Future is covariant, and Timestamped and Notification are probably, too. |
RxJava-pull-requests #218 SUCCESS |
RxJava-pull-requests #219 FAILURE |
Huh? - Very interesting (compiles for me)... |
…is unhappy otherwise?
Here is a trivial example using Java 8 demonstrating how Observable<String> oMovie = Observable.create((Observer<? super Movie> o) -> {
o.onNext(new Movie());
o.onNext(new Movie());
return Subscriptions.empty();
}).map((movie) -> {
return "movie transformed: " + movie;
});
Observable<String> oMedia = Observable.create((Observer<? super Media> o) -> {
o.onNext(new Media());
o.onNext(new HorrorMovie());
return Subscriptions.empty();
}).map((movie) -> {
return "media transformed: " + movie;
});
Observable.zip(oMovie, oMedia, (a, b) -> {
return a + " ----- " + b;
}).subscribe((movie) -> {
System.out.println("Media/Movie: " + movie);
}); Or type safety can be thrown away: Observable<String> oMovie = Observable.create((Observer o) -> {
o.onNext(new Movie());
o.onNext(new Movie());
return Subscriptions.empty();
}).map((movie) -> {
return "movie transformed: " + movie;
}); But now that allows this to compile: Observable<String> oMovie = Observable.create((Observer o) -> {
o.onNext(new Movie());
o.onNext("hello"); // this is not a Movie object
return Subscriptions.empty();
}).map((movie) -> {
return "movie transformed: " + movie;
}); |
@jmhofer The @mttkay and @mustafasezgin Do you have any input on this discussion since you are using RxJava from plain Java? |
Groovy is similar ... but the generics don't actually do much for it as they are more-or-less ignored at compile time as best I can tell: Observable<String> oMovie = Observable.create({ Observer o ->
o.onNext(new Movie());
o.onNext(new Movie());
return Subscriptions.empty();
}).map({ movie ->
return "movie transformed: " + movie;
});
Observable<String> oMedia = Observable.create({ Observer<? super Media> o ->
o.onNext(new Media());
o.onNext(new HorrorMovie());
return Subscriptions.empty();
}).map({ movie ->
return "media transformed: " + movie;
});
Observable.zip(oMovie, oMedia, { a, b ->
return a + " ----- " + b;
}).subscribe({ movie ->
System.out.println("Media/Movie: " + movie);
}); |
This Groovy code works against current master as well as the new code with Observable<Movie> oMovie = Observable.create({ Observer<? super Movie> o ->
o.onNext(new Movie());
o.onNext(new Movie());
o.onCompleted();
return Subscriptions.empty();
});
Observable<Media> oMedia = Observable.create({ Observer<? super Media> o ->
o.onNext(new Media());
o.onNext(new HorrorMovie());
o.onCompleted();
return Subscriptions.empty();
});
Observable.zip(oMovie, oMedia, { Movie a, Media b ->
return String.valueOf(a) + " ----- " + String.valueOf(b);
}).subscribe({ media ->
System.out.println("Media/Movie: " + media);
}); And here it is again in plain Java: Observable<Movie> oMovie = Observable.create(new Func1<Observer<? super Movie>, Subscription>() {
@Override
public Subscription call(Observer<? super Movie> o) {
o.onNext(new Movie());
o.onNext(new Movie());
o.onCompleted();
return Subscriptions.empty();
}
});
Observable<Media> oMedia = Observable.create(new Func1<Observer<? super Media>, Subscription>() {
@Override
public Subscription call(Observer<? super Media> o) {
o.onNext(new Media());
o.onNext(new HorrorMovie());
o.onCompleted();
return Subscriptions.empty();
}
});
Observable.zip(oMovie, oMedia, new Func2<Movie, Media, String>() {
@Override
public String call(Movie a, Media b) {
return String.valueOf(a) + " ----- " + String.valueOf(b);
}
}).subscribe(new Action1<String>() {
@Override
public void call(String media) {
System.out.println("Media/Movie: " + media);
}
}); It seems that |
It seems the only option (while supporting covariance) for reducing code verbosity is to create a new type that hides the So my question now is: should we change the API to make all uses of In other words, do we leave it as this: public static <T> Observable<T> create(Func1<? super Observer<? super T>, ? extends Subscription> func) or change it to public static <T> Observable<T> create(ObservableFunction<T> func) And what do we call the new type if we go that route? Options I've considered are:
And should that live in Unfortunately we can not overload this method and support both as type erasure makes them the same (and it will confuse dynamic languages, implicits etc) if we had two methods with similar single function signatures. Normally I'd rather leave the lower level |
One last spam to everyone ... please weigh in if you have an opinion, as I intend on making a decision and releasing this week. This will be a breaking change and affect usage for everyone. |
Here is what the /**
* Function interface for work to be performed when an {@link Observable} is subscribed to via {@link Observable#subscribe(Observer)}
*
* @param <T>
*/
public interface ObservableFunction<T> extends Func1<Observer<? super T>, Subscription> {
public Subscription call(Observer<? super T> t1);
} And the updated public static <T> Observable<T> create(ObservableFunction<T> func) And sample code using this: Observable<Movie> oMovie = Observable.create(new ObservableFunction<Movie>() {
@Override
public Subscription call(Observer<? super Movie> o) {
o.onNext(new Movie());
o.onNext(new Movie());
o.onCompleted();
return Subscriptions.empty();
}
}); |
Here is a fork including these changes so we can review and discuss: benjchristensen@de0358f I'm still not thrilled by any of the directions we can take here. I can't argue against making RxJava support covariant types. Josh Bloch certainly supports it in Effective Java when he says "If you write a library that will be widely used, the proper use of wildcard types should be considered mandatory." On the flip-side, it forces the use of '? super/? extends' everywhere even when covariant requirements are rare. I think the only decision at this point to make is whether we should use something like Other operators that are awkward include:
FlatMap is very common and ends up like this: oMedia.flatMap(new Func1<Media, Observable<? extends String>>() {
@Override
public Observable<? extends String> call(Media s) {
...
}
}); Compare this with current: oMedia.flatMap(new Func1<Media, Observable<String>>() {
@Override
public Observable<String> call(Media s) {
...
}
}); However, lambdas do make a big different on instance methods (not as much on statics) as they can infer the types. For example, in Java 8 the above can become this: oMedia.flatMap(value -> {
return null;
}); So without Observable.create((Observer<? super Media> o) -> {
o.onNext(new Media());
o.onNext(new HorrorMovie());
return Subscriptions.empty();
}).flatMap(movie -> {
return Observable.from("media transformed: " + movie.getName());
}); With Observable.create((Observer<Media> o) -> {
o.onNext(new Media());
o.onNext(new HorrorMovie());
return Subscriptions.empty();
}).flatMap(movie -> {
return Observable.from("media transformed: " + movie.getName());
}); It seems that type inference will handle the instance methods. For example, Observable.create((Observer<Integer> o) -> {
o.onNext(1);
o.onNext(2);
return Subscriptions.empty();
}).aggregate((previous, current) -> {
return previous + current;
}); So it's only the static methods of concern, Java 6/7 (and Android) are going to be ugly no matter what - but we already knew that and they already are. This makes it worse. |
Experiencing some of the pain with generic method signatures and nesting |
RxJava-pull-requests #229 FAILURE |
+1 ObservableFunction interface but call it something else maybe with subscription/subscribe in the name. |
@benjchristensen We could overload I'm not sure about the naming. Although it is kind of the internal |
RxJava-pull-requests #230 SUCCESS |
How about these names?
|
Based on my highly scientific poll of people around me at my office ... and the few who have commented above, I'm going with |
@benjchristensen works for me. Will it extend |
I haven't played with it not extending Func1. It will need to at least extend |
Since this function is only intended for us by the This works great in Java, are there any issues from Clojure or Scala doing it this way? Create looks like this: public static <T> Observable<T> create(OnSubscribeFunc<T> func) Use of it looks like this: import rx.Observable;
import rx.Observable.OnSubscribeFunc;
Observable<String> observable = Observable.create(new OnSubscribeFunc<String>() {
@Override
public Subscription call(Observer<? super String> Observer) {
Observer.onNext("one");
Observer.onNext("two");
Observer.onNext("three");
Observer.onCompleted();
return Subscriptions.empty();
}
}); The function definition looks like: /**
* Function interface for work to be performed when an {@link Observable} is subscribed to via {@link Observable#subscribe(Observer)}
*
* @param <T>
*/
public static interface OnSubscribeFunc<T> extends Function<T> {
public Subscription call(Observer<? super T> t1);
} |
Good idea, and shouldn't be a problem for Scala. |
Great, I'll proceed with this change then. Thanks for the feedback. |
Final interface looks like this: /**
* Function interface for work to be performed when an {@link Observable} is subscribed to via {@link Observable#subscribe(Observer)}
*
* @param <T>
*/
public static interface OnSubscribeFunc<T> extends Function {
public Subscription onSubscribe(Observer<? super T> t1);
} This is being merged in #343 |
This is related to ongoing work related to covariant support at ReactiveX#331
This is related to ongoing work related to covariant support at ReactiveX#331
This is related to ongoing work related to covariant support at ReactiveX/RxJava#331
This is related to ongoing work related to covariant support at ReactiveX/RxJava#331
…ntimeExce… (ReactiveX#389) * Issue ReactiveX#331: Fixed Retry.decorateCallable. It handled only RuntimeExceptions, but should handle Exceptions instead. * Issue ReactiveX#331: CircuitBreaker not handle java.lang.Error, but only java.lang.Exception.
Ok, so this pull request changes a lot of lines. It's mostly generalizing all the
FuncX
s to be used likeFuncX[-T1, -T2, ..., -TX, +R]
(contravariant parameters, covariant return type) and all theObserver
s to be used "in a contravariant way". A few of theObservable
uses are covariant, now, too (mostlyzip
).This is the pull request for #326.
This doesn't look very good in the code (thanks Java). Also, it doesn't seem to make Scala interop easier at all (at least not yet).
Please take a look. I'm not exactly happy with the result. - Maybe I'm doing something wrong here? - I've still got hope that there's an easier way...
The pull request compiles and tests ok for me (except for the Clojure module, but that's another story and not due to my changes).