Skip to content

Conversation

akarnokd
Copy link
Member

@akarnokd akarnokd commented Nov 6, 2015

It comes up quite frequently that one wants to map values and throw exceptions from within the same function (for example, when dealing with blocking IO that may throw IOException). It can be inconvenient to use try-catch and gift-wrap the checked exception into a RuntimeException.

This PR adds a Func1E functional interface which has a generic exception parameter as well and the operator mapIO that accepts it. Changing the existing map is out of question because of binary compatibility.

@JakeWharton
Copy link
Contributor

Why mapIO if it's agnostic to any checked exception? I realize that's the motivating use case, but it's not the implementation (nor does it have anything to do with IO).

@akarnokd
Copy link
Member Author

akarnokd commented Nov 6, 2015

The operator naming is up for discussion, I suggested mapIO because this is the shortest one that made sense to me. We can't overload map() due to dynamic languages and problems with arity-based lambda disambiguation in compilers.

@davidmoten
Copy link
Collaborator

Perhaps mapChecked.

By the way in v2 have we considered designing Func* so it throws Exception (generic or otherwise)? It strikes me as quite a drawback of v1 that when say an IOException is thrown by a Func1 that that exact exception isn't the one that arrives at the subscriber. Instead we are forced to throw a RuntimeException of some kind which though it might wrap the IOException is not it! Of course there would be the added benefit of no try catch blocks as well.

Incidentally for Java 8 purposes I use a utility class I call Checked like below which wraps Actions and Funcs with RuntimeExceptions:

source.doOnNext(Checked.a1(s -> os.write(s.getBytes())))

Though Checked is reasonably convenient I would still prefer that the exact Exception thrown arrived at the subscriber in general. This is presumably doable in v2 (and @abersnaze had some nifty ideas for v1 ages ago in #1305).

@akarnokd
Copy link
Member Author

akarnokd commented Nov 6, 2015

In current v2, I'm using as much Java 8 as possible and unfortunately, Java 8 doesn't have standard functional interfaces that specify throws. I know that map is a common pain point. Given mapIO, you can replace the sideeffecting doOnNext with it so we can avoid drastic API expansion.

I've been also thinking about a mapAction operator where the lambda function has options to return a value, skip a value, throw/complete with an error and complete the sequence. Like fusing mapIO, filter and takeWhile into a single access point.

interface MapActions<T, R, E extends Exception> {
    T input();
    void output(R r);
    void error(E e);
    void skip();
    void stop();
}

public <R, E extends Exception> Observable<R> mapAction(
    Action1E<MapActions<T, R, E>, E> mapper);

}
};

final Func1E<Integer, Integer, Exception> TRHOW_CHECKED = new Func1E<Integer, Integer, Exception>() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spelling

@abersnaze
Copy link
Contributor

I'm not to fond of a more functions on rx.Observable, operator implementation code, and test for implementation. I guess I would be ok with @davidmoten Checked but would've preferred #1305.

@stealthcode
Copy link

I would also prefer the changes in #1305. Cursory tests of the solution in @abersnaze's pull request show that it's simple to use either a checked interface or unchecked.

public interface Action1Checked<T1> extends Action {
    public void call(T1 t1) throws Exception;
}
public interface Action1<T1> extends Action1Checked<T1> {
    public void call(T1 t1);
}

public static void main(String[] args) {
    bar(o->{});
    foo(o->{throw new Exception();});
}

public static void foo(Action1Checked<Object> a) {
    try {
        a.call(new Object());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static void bar(Action1<Object> a) {
    a.call(new Object());
}

@benjchristensen @headinthebox @akarnokd how should this operate in 2.x?

a) support throwing checked exceptions
b) have both checked and unchecked variant of each operator that's popular enough to warrant one
c) current support model of just funcs/actions that throw only runtime exceptions

I think we should support this case first class instead of fight against the language feature. We can't force users to write funcs and actions where all exceptions are appropriately caught and handled so the code to handle any runtime exception must be written and maintained.

@akarnokd
Copy link
Member Author

Maybe this would just confuse people using 1.x so let's stick to utility wrappers (or perhaps sneaky throwers?).

As for 2.x, fromCallable already allows a throwing lambda and me may go for custom FunctionE for map and flatMap as these seem to be the main targets for checked-exception requiring operations.

@artem-zinnatullin
Copy link
Contributor

I'm afraid that if we will start adding exception-tolerant versions of operators we will end up 1.5-2xing amount of operators in RxJava 1.x.

@akarnokd, others: what if we will make all or as much as possible operators exception-tolerant in RxJava 2.x and leave 1.x as is?

@akarnokd
Copy link
Member Author

akarnokd commented Dec 8, 2015

I'm closing this and we can revisit the problem in 2.x later on.

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

Successfully merging this pull request may close these issues.

6 participants