Skip to content
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

mergeMap/flatMap and Java 8 type inference #1550

Closed
benjchristensen opened this issue Aug 4, 2014 · 15 comments
Closed

mergeMap/flatMap and Java 8 type inference #1550

benjchristensen opened this issue Aug 4, 2014 · 15 comments
Labels
Milestone

Comments

@benjchristensen
Copy link
Member

@adelnizamutdinov I don't see a new issue created, so wanted to pick it up here.

You said this:

mergeMap is almost unusable with Java 8 type inference
Always have to extend Observable::just and Observable::empty to anonymous classes

Can you elaborate?

@benjchristensen
Copy link
Member Author

        Observable.from(1, 2, 3, 4).mergeMap(i -> {
            if (i < 3) {
                return Observable.just("value_" + i);
            } else {
                return Observable.empty();
            }
        }).forEach(System.out::println);

@benjchristensen
Copy link
Member Author

It also correctly infers it is a String:

        Observable<String> mergeMap = Observable.from(1, 2, 3, 4).mergeMap(i -> {
            if (i < 3) {
                return Observable.just("value_" + i);
            } else {
                return Observable.empty();
            }
        });

@meoyawn
Copy link

meoyawn commented Aug 5, 2014

@benjchristensen
here's the example that won't compile because of the variance/contravariance and java 8 lambda's type inference:

static class User {
    String name;
    int age;

    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public static void main(String[] args) {
    savedUser()
            .mergeMap(Observable::just, e -> fetchUser(), Observable::empty)
            .forEach(user -> System.out.println(user.name));
}

static Observable<User> savedUser() {
    return Observable.error(new IllegalStateException("there are no saved users"));
}

static Observable<User> fetchUser() {
    return Observable.just(new User("Adel", 13));
}

@benjchristensen
Copy link
Member Author

This code compiles and runs for me:

import rx.Observable;


public class Issue1550 {

    static class User {
        String name;
        int age;

        User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

    public static void main(String[] args) {
        savedUser()
                .mergeMap(Observable::just, e -> fetchUser(), Observable::empty)
                .forEach(user -> System.out.println(user.name));
    }

    static Observable<User> savedUser() {
        return Observable.error(new IllegalStateException("there are no saved users"));
    }

    static Observable<User> fetchUser() {
        return Observable.just(new User("Adel", 13));
    }

}

It outputs:

Adel

@benjchristensen
Copy link
Member Author

Ah interesting, it works in Eclipse, but not from command line.

lgml-bechristensen:libs benjchristensen$ /Library/Java/JavaVirtualMachines/jdk1.8.0_0.jdk/Contents/Home/bin/javac -cp rxjava-core-0.20.0-RC4-SNAPSHOT.jar Issue1550.java 
Issue1550.java:18: error: incompatible types: no instance(s) of type variable(s) T#1 exist so that Observable<T#1> conforms to ? extends Observable<? extends R>
                .mergeMap(Observable::just, e -> fetchUser(), Observable::empty)
                         ^
  where T#1,R,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>empty()
    R extends Object declared in method <R>mergeMap(Func1<? super T#2,? extends Observable<? extends R>>,Func1<? super Throwable,? extends Observable<? extends R>>,Func0<? extends Observable<? extends R>>)
    T#2 extends Object declared in class Observable
1 error

benjchristensen added a commit to benjchristensen/RxJava that referenced this issue Aug 8, 2014
benjchristensen added a commit to benjchristensen/RxJava that referenced this issue Aug 8, 2014
@benjchristensen
Copy link
Member Author

I believe this is now fixed as of #1558.

If you can confirm against the branch, please do. Reopen if there are still issues.

@meoyawn
Copy link

meoyawn commented Aug 8, 2014

@benjchristensen I don't have the rights to reopen the issues
but I cloned the master, built the jar and tried to compile:

$JAVA8_HOME/bin/javac Test.java -cp rxjava-core-0.20.0-RC5-SNAPSHOT.jar
Test.java:19: error: incompatible types: no instance(s) of type variable(s) T#1 exist so that Observable<T#1> conforms to ? extends Observable<? extends R>
                .mergeMap(Observable::just, e -> fetchUser(), Observable::empty)
                         ^
  where T#1,R,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>empty()
    R extends Object declared in method <R>mergeMap(Func1<? super T#2,? extends Observable<? extends R>>,Func1<? super Throwable,? extends Observable<? extends R>>,Func0<? extends Observable<? extends R>>)
    T#2 extends Object declared in class Observable

I don't really think this is an issue, because expanding the method ref as a new Func1... works just fine, but is ugly in java8
so I thought maybe it's me who's doing something wrong
I am very bad with co- and contravariance so I can't even figure out how to change mergeMap's signature so that the thing compiles with method references

@meoyawn
Copy link

meoyawn commented Aug 8, 2014

@benjchristensen I'm using Oracle JDK on Ubuntu 14.04

$JAVA8_HOME/bin/javac -version
javac 1.8.0_11

@benjchristensen
Copy link
Member Author

@zsxwing You're good at figuring this kind of stuff out, so is there anything we should be doing differently here, or is this just a weakness of Java 8?

@benjchristensen benjchristensen added this to the 1.0 milestone Oct 7, 2014
@zsxwing
Copy link
Member

zsxwing commented Oct 8, 2014

@benjchristensen actually, I know little about Java type inference. I try the following codes and it works:

import rx.Observable;
import rx.functions.*;

public class Issue1550 {

    static class User {
        String name;
        int age;

        User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

    public static void main(String[] args) {
        Func0<Observable<User>> empty = () -> Observable.empty();
        Func1<User, Observable<User>> just = (User user) -> Observable.just(user);
        savedUser().flatMap(
                just,
                e -> fetchUser(),
                empty)
        .forEach(user -> System.out.println(user.name));
    }

    static Observable<User> savedUser() {
        return Observable.error(new IllegalStateException("there are no saved users"));
    }

    static Observable<User> fetchUser() {
        return Observable.just(new User("Adel", 13));
    }

}

So I guess it's a weakness of Java type inference. Looks we have to write a temp variable to tell the compiler type information.

@zsxwing
Copy link
Member

zsxwing commented Oct 8, 2014

The following codes work, too:

import rx.Observable;
import rx.functions.*;

public class Issue1550 {

    static class User {
        String name;
        int age;

        User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

    public static void main(String[] args) {
        Func0<Observable<User>> empty = Observable::empty;
        Func1<User, Observable<User>> just = Observable::just;
        savedUser().flatMap(
                just,
                e -> fetchUser(),
                empty)
        .forEach(user -> System.out.println(user.name));
    }

    static Observable<User> savedUser() {
        return Observable.error(new IllegalStateException("there are no saved users"));
    }

    static Observable<User> fetchUser() {
        return Observable.just(new User("Adel", 13));
    }

}

@akarnokd
Copy link
Member

akarnokd commented Oct 8, 2014

Java 8 type inference has some weaknesses, and javac won't compile code that will compile just fine in Eclipse 4.4. Based on some comments in lambda-dev, those javac shortcommings won't be fixed until Java 9.

@benjchristensen
Copy link
Member Author

so @akarnokd is it your assessment that there is not anything better we can do with the RxJava APIs and it's just up to the language/compiler itself?

@akarnokd
Copy link
Member

Java 6 and 7 developers would need the full covariance in the parameter types and only Java 8 non-Eclipse developers have to do workarounds such as above. Besides, the Stream.flatMap has full covariance in its signature:

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

So it is either lambda workarounds for Java 8 devs or unchecked casting for Java 6/7 to bypass the lack of variance.

@benjchristensen
Copy link
Member Author

I'm considering this "as good as it can get" then since our flatMap matches the Stream interface definition:

<R> Observable<R> flatMap(Func1<? super T, ? extends Observable<? extends R>> func)

Thanks @akarnokd for the insight.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants