-
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
Observable.compose Generics #1663
Comments
public <R> Observable<R> compose(Transformer<? super T, R> transformer) {
return transformer.call(this);
}
/**
* Transformer function used by {@link #compose}.
* @warn more complete description needed
*/
public static interface Transformer<T, R> extends Func1<Observable<? extends T>, Observable<R>> {
// cover for generics insanity
} This is what it is right now ... it's not right. |
Either @headinthebox or @benjchristensen will buy a beer for whoever solves this! |
This compiles for me in Eclipse 4.4:
The sad thing is that the types need to be reinforced at more places and the chain split into several parts. |
@benjchristensen could you try #1701? |
This may be fixed via #1701 ... keeping open until fully confirming in systems using 1.0.0-rc.4 |
This is still hard to use ... I haven't figured this out for example: @Test
public void testComposeWithDeltaLogic() {
List<Movie> list1 = Arrays.asList(new Movie(), new HorrorMovie(), new ActionMovie());
List<Movie> list2 = Arrays.asList(new ActionMovie(), new Movie(), new HorrorMovie(), new ActionMovie());
Observable<List<Movie>> movies = Observable.just(list1, list2);
movies.compose(deltaTransformer);
}
static Transformer<Observable<? extends List<? super Movie>>, Observable<? super Movie>> deltaTransformer = new Transformer<Observable<? extends List<? super Movie>>, Observable<? super Movie>>() {
@Override
public Observable<? extends Observable<? super Movie>> call(Observable<? extends Observable<? extends List<? super Movie>>> movieList) {
return movieList
.startWith(new ArrayList<Movie>())
.buffer(2, 1)
.skip(1)
.flatMap(calculateDelta);
}
};
static Func1<List<List<Movie>>, Observable<Movie>> calculateDelta = new Func1<List<List<Movie>>, Observable<Movie>>() {
public Observable<Movie> call(List<List<Movie>> listOfLists) {
if (listOfLists.size() == 1) {
return Observable.from(listOfLists.get(0));
} else {
// diff the two
List<Movie> newList = listOfLists.get(1);
List<Movie> oldList = new ArrayList<Movie>(listOfLists.get(0));
Set<Movie> delta = new LinkedHashSet<Movie>();
delta.addAll(newList);
// remove all that match in old
delta.removeAll(oldList);
// filter oldList to those that aren't in the newList
oldList.removeAll(newList);
// for all left in the oldList we'll create DROP events
for (Movie old : oldList) {
delta.add(new Movie());
}
return Observable.from(delta);
}
};
}; |
If we can't get |
The following codes can work: @Test
public void testComposeWithDeltaLogic() {
List<Movie> list1 = Arrays.asList(new Movie(), new HorrorMovie(), new ActionMovie());
List<Movie> list2 = Arrays.asList(new ActionMovie(), new Movie(), new HorrorMovie(), new ActionMovie());
Observable<List<Movie>> movies = Observable.just(list1, list2);
movies.compose(deltaTransformer);
}
static Transformer<List<Movie>, Movie> deltaTransformer = new Transformer<List<Movie>, Movie>() {
@Override
public Observable<? extends Movie> call(Observable<? extends List<Movie>> o) {
Observable<List<Movie>> movieList = (Observable<List<Movie>>)o;
return movieList
.startWith(new ArrayList<Movie>())
.buffer(2, 1)
.skip(1)
.flatMap(calculateDelta);
}
};
static Func1<List<List<Movie>>, Observable<Movie>> calculateDelta = new Func1<List<List<Movie>>, Observable<Movie>>() {
public Observable<Movie> call(List<List<Movie>> listOfLists) {
if (listOfLists.size() == 1) {
return Observable.from(listOfLists.get(0));
} else {
// diff the two
List<Movie> newList = listOfLists.get(1);
List<Movie> oldList = new ArrayList<Movie>(listOfLists.get(0));
Set<Movie> delta = new LinkedHashSet<Movie>();
delta.addAll(newList);
// remove all that match in old
delta.removeAll(oldList);
// filter oldList to those that aren't in the newList
oldList.removeAll(newList);
// for all left in the oldList we'll create DROP events
for (Movie old : oldList) {
delta.add(new Movie());
}
return Observable.from(delta);
}
};
}; |
So the problem of |
In the operators |
@akarnokd so we should remove |
I guess in |
The reason for For |
I've been using compose of late and +1 for
|
How would |
We're using compose and it's very convenient! public static <T> Observable.Transformer<Optional<T>, T> ifAbsentThrow(Func0<RuntimeException> absentCase) {
return source -> source.map(optional -> {
if (optional.isPresent()) {
return optional.get();
}
throw absentCase.call();
});
}
userQueryObservables.findByUsername(username)
.compose(ifAbsentThrow(() -> new UsernameNotFoundException(username)))
.flatMap(user -> ...) |
Would you recommend any signature changes based on your usage? |
We've hit the issue described above in a couple of places, so we had to suppress the unchecked warning. Have you considered creating a @SuppressWarnings("unchecked")
public <R> Observable<R> compose(Transformer<T, ? extends R> transformer) {
return (Observable<R>) transformer.call(this);
}
@SuppressWarnings("unchecked")
public <R> Observable<R> compose(CovariantTransformer<? super T, ? extends R> transformer) {
return (Observable<R>) transformer.call(this);
}
public static interface Transformer<T, R> extends Func1<Observable<T>, Observable<? extends R>> {
}
public static interface CovariantTransformer<T, R> extends Func1<Observable<? extends T>, Observable<? extends R>> {
} then your example above would be: @Test
public void testComposeWithDeltaLogic() {
List<Movie> list1 = Arrays.asList(new Movie(), new HorrorMovie(), new ActionMovie());
List<Movie> list2 = Arrays.asList(new ActionMovie(), new Movie(), new HorrorMovie(), new ActionMovie());
Observable<List<Movie>> movies = Observable.just(list1, list2);
movies.compose(deltaTransformer);
}
static Transformer<List<Movie>, Movie> deltaTransformer = new Transformer<List<Movie>, Movie>() {
@Override
public Observable<? extends Movie> call(Observable<List<Movie>> movieList) {
return movieList
.startWith(new ArrayList<Movie>())
.buffer(2, 1)
.skip(1)
.flatMap(calculateDelta);
}
};
static Func1<List<List<Movie>>, Observable<Movie>> calculateDelta = new Func1<List<List<Movie>>, Observable<Movie>>() {
public Observable<Movie> call(List<List<Movie>> listOfLists) {
if (listOfLists.size() == 1) {
return Observable.from(listOfLists.get(0));
} else {
// diff the two
List<Movie> newList = listOfLists.get(1);
List<Movie> oldList = new ArrayList<Movie>(listOfLists.get(0));
Set<Movie> delta = new LinkedHashSet<Movie>();
delta.addAll(newList);
// remove all that match in old
delta.removeAll(oldList);
// filter oldList to those that aren't in the newList
oldList.removeAll(newList);
// for all left in the oldList we'll create DROP events
for (Movie old : oldList) {
delta.add(new Movie());
}
return Observable.from(delta);
}
};
}; In @Test
public void testCovarianceOfCompose() {
Observable<HorrorMovie> movie = Observable.just(new HorrorMovie());
Observable<Movie> movie2 = movie.compose(new CovariantTransformer<Movie, Movie>() {
@Override
public Observable<? extends Movie> call(Observable<? extends Movie> t1) {
return Observable.just(new Movie());
}
});
} |
I haven't and it concerns me somewhat because of type erasure and impact on dynamic languages as they would not be able to disambiguate between the two. Are these issues just weaknesses with Java or are we doing something wrong? |
What about #1762? |
#1762 seems to work better. The following code using Java 8 compiles with #1762 but not with what is in trunk: import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import rx.Observable;
public class ComposeExample2 {
public static void main(String[] args) {
List<Movie> list1 = Arrays.asList(new Movie(), new HorrorMovie(), new ActionMovie());
List<Movie> list2 = Arrays.asList(new ActionMovie(), new Movie(), new HorrorMovie(), new ActionMovie());
Observable<List<Movie>> movies = Observable.just(list1, list2);
Observable<Movie> compose = movies.compose(ComposeExample2::transform);
compose.subscribe(System.out::println);
}
public static Observable<Movie> transform(Observable<List<Movie>> movieList) {
return movieList
.startWith(new ArrayList<Movie>())
.buffer(2, 1)
.skip(1)
.flatMap(ComposeExample2::calculateDelta);
}
public static Observable<Movie> calculateDelta(List<List<Movie>> listOfLists) {
if (listOfLists.size() == 1) {
return Observable.from(listOfLists.get(0));
} else {
// diff the two
List<Movie> newList = listOfLists.get(1);
List<Movie> oldList = new ArrayList<Movie>(listOfLists.get(0));
Set<Movie> delta = new LinkedHashSet<Movie>();
delta.addAll(newList);
// remove all that match in old
delta.removeAll(oldList);
// filter oldList to those that aren't in the newList
oldList.removeAll(newList);
// for all left in the oldList we'll create DROP events
for (Movie old : oldList) {
delta.add(new Movie());
}
return Observable.from(delta);
}
};
/*
* Most tests are moved into their applicable classes such as [Operator]Tests.java
*/
static class Media {
}
static class Movie extends Media {
}
static class HorrorMovie extends Movie {
}
static class ActionMovie extends Movie {
}
static class Album extends Media {
}
static class TVSeason extends Media {
}
static class Rating {
}
static class CoolRating extends Rating {
}
static class Result {
}
static class ExtendedResult extends Result {
}
} Thank you @vadims |
Problem is the obvious candidate is a Func1 and the method cannot be passed
|
@davidmoten Are you saying you'd prefer this: public <R> Observable<R> compose(Func1<? super Observable<T>, ? extends Observable<? extends R>> transformer) {
// Casting to Observable<R> is type-safe because we know Observable is covariant.
return (Observable<R>) transformer.call(this);
} If we went that way then code like this: private static Transformer<? super String, String> appendWorldTransformer() {
return o -> o.map(s -> s + " world!").finallyDo(() -> {
System.out.println(" some side-effect");
});
} would need to change to this: private static Func1<Observable<? super String>, Observable<? extends String>> appendWorldTransformer() {
return o -> o.map(s -> s + " world!").finallyDo(() -> {
System.out.println(" some side-effect");
});
} Is that what you're suggesting? In Java 8 a method like this works with either signature: public static Observable<Movie> transform(Observable<List<Movie>> movieList) {
return movieList
.startWith(new ArrayList<Movie>())
.buffer(2, 1)
.skip(1)
.flatMap(ComposeExample2::calculateDelta);
} Originally the reason for the I still can't figure out how to make this code work using Observable<Movie> movie2 = movie.compose(new Func1<Observable<Movie>, Observable<? extends Movie>>() {
@Override
public Observable<? extends Movie> call(Observable<Movie> t1) {
return Observable.just(new Movie());
}
}); Here is the error:
So if we're going to use |
Take a look at #1770 (comment) for example of code that needs to be made easy to write with |
I don't have a strong preference to using The type of code we should be able to easily write is shown above in #1663 (comment) |
I'd like to make a decision on this soon and move forward. The best option I've seen so far is #1762 |
Anyone have a better alternative than #1762 that makes the code examples above work? If not I'm proceeding with it. |
Re the |
No that can't be done because of type erasure.
One of the reasons we didn't do that for The other reason is that doing that seems to break some of the co/contra-variance use cases, such as this one: |
Just to be clear ... as I said earlier in the thread, if we can achieve co/contra-variance on the unit tests and make the example code (#1663 (comment)) work using just |
I went with @vadims proposal as I have not seen anything else achieve the desired combination of usage simplicity and support for co/contra-variance. It was merged in #1776. I am not releasing the next version for at least 24 hours so if anyone wishes to debate this further that is the window of time to offer an alternative that works with the committed unit tests and the Java 8 code shown above (#1663 (comment)). Thank you everyone for your help in figuring this out and looking at various alternative. (Yet again I am reminded of how little I like working with co/contra-variant generics in Java.) |
@benjchristensen could you take a look at #1778? |
I will later today. |
The generics still don't work on
compose
.The text was updated successfully, but these errors were encountered: