Why isn't there a separate Future-like class for one-shot async tasks?

304 views
Skip to first unread message

Jong Wook Kim

unread,
Feb 21, 2014, 1:24:05 PM2/21/14
to rxj...@googlegroups.com
After a successful rewrite of my sync codebase to async with an enormous help of RxJava, 

I started to wonder why it doesn't have a Future type that represents a one-time task rather than an asynchronous push-based collection of data.

I have seen a few Netflix presentations on RxJava saying that Observable can be used for both async collections and one-time async tasks, which is nice,

but it's a little confusing when I have an Observable<T> and I can't really tell, by looking at the type, whether it will emit a series of data or just a single object.

For example, my Observable wrapper on Netflix Curator has its interface like this:

Observable<String> watch(String path);
Observable<String> get(String path);
Observable<Void> delete(String path);

The method watch returns an infinite stream of String objects which is emitted every time the ZooKeeper node's data is updated,

whereas the second and third ones are for one-time asynchronous tasks, which will emit a single response when the task finishes, and then complete the Observable.

I wish I had a 'Future' type to distinguish these from a collection Observable, so that I can notice how an asynchronous operation will behave just by looking at the type, and become more typesafe in a sense.

I am aware that this whole project is about the idea of Observable as the async Iterable, and there are Scala Future, Akka future, Guava ListenableFuture and Java 8's CompletableFuture for my concerns,

But I just can't give up all the powerful functionalities that Rx provides, and I also don't want to write and use messy wrappers over those external Future classes.

If there is a rx.Future type that is interopable with rx.Observable, possibly as a subclass of rx.Observable,

the interfaces can still be consistent, like merge(Future...) returning an Observable and zip(Future...) returning a Future, Future.map/flatMap returns Future while Observable.map/flatMap returns Observable, and so on.

Is there some release notes/discussions that explain why RxJava didn't adopt this strategy? I know my whole idea might be wrong so advises are appreciated.

Thanks,

Jong Wook


Ben Christensen

unread,
Feb 21, 2014, 6:55:28 PM2/21/14
to Jong Wook Kim, rxj...@googlegroups.com
I’m open to the idea and it has been discussed before. The primary reason why we haven’t pursued this is because as soon as a Task/Future is combined with an Observable the type is lost and it becomes Observable again. 

When discussing whether we should do it we’ve considered using the name Task instead of Future to differentiate from the many other Futures on the JVM for two reasons:

1) Task is what .Net has with Observable
2) An Observable “Task” is slightly different than a Future since a Future is “hot” in that once returned it can only be used once. It can’t be subscribe to multiple times whereas a Task could. We don’t want to confuse with Future behavior if we do go down this path.

Other weaker arguments against it have been:

- even a primitive int has multiple values that must be validated (such as >0)
- rebuttal is this is why enums are used
- Futures have multiple states that must be validated such as isCompleted, isFailed, isSuccess and then fetching the value, so why is asserting length()==1 any different by using something like Observable.first() or Observable.take(1)

Assuming we want to pursue this idea, it couldn’t be a subclass of Observable because many of the operators won’t make sense. This also means that combining with an Observable requires some kind of common type or various method overloads that handle Observable and/or Task for the combinatorial operators: merge, zip, concat, combineLatest, etc. I would prefer not having new Task specific Subscriber/Observer types either, but then it’s somewhat odd as to why there would be both onNext and onCompleted when a single onNext implies onCompleted.

-- 
Ben Christensen
+1.310.782.5511  @benjchristensen

Matthias

unread,
Feb 25, 2014, 11:24:44 AM2/25/14
to rxj...@googlegroups.com, Jong Wook Kim
I'm actually down with the idea of observables that emit just one item; we use that quite a bit. In our UI we never process item individually, but always in batches, and not emitting them in a batch would add complexity in the observers. Using observables that emit single items still adds tons of value, since we can chain up tasks and propagate results along the call chain via mergeMap.

I agree that it all falls apart whenever your sequence isn't actually a sequence, but some sort of unit action / procedure. Observables only make sense when using real functions, since there's nothing to map or transform when there's not return value (i.e. call to onNext.)

We do miss this functionality too. On Android there's framework classes that are fit to that purpose, however, it would be nice to have just one way to process async tasks, not 2. Therefore we often emit superficial values through onNext such as "task results" or simply booleans, so that these tasks remain chainable. It feels a bit off though. Would be interested to see how a one-off tasks solution could fit into the existing library.

Samuel Grütter

unread,
Mar 4, 2014, 1:47:58 AM3/4/14
to Matthias, rxj...@googlegroups.com, Jong Wook Kim
I like the idea of having something like a OneElementObservable (without confusing it with a Future, see Ben's point 2)). With this, we could carry the information that exactly one item can be expected in the type, instead of depending on documentation to do so. Errors like this one: https://github.com/Netflix/RxJava/issues/564 could be avoided.

Ben Christensen

unread,
Mar 4, 2014, 7:41:52 PM3/4/14
to Samuel Grütter, Matthias, Jong Wook Kim, rxj...@googlegroups.com
How would it solve issue 564 which was about zipping streams together?

The type would only carry the information until it is composed with an Observable, then it would be lost. It would still be useful for some things, but the type information would be easy to lose in any combinatorial call graph (such as n number of network calls merged together).

-- 
Ben Christensen
+1.310.782.5511  @benjchristensen

Samuel Grütter

unread,
Mar 5, 2014, 10:32:20 AM3/5/14
to Ben Christensen, Matthias, Jong Wook Kim, rxj...@googlegroups.com

The idea would be that "aggregation functions" like reduce, all, min, max, toList, sequenceEqual etc would return a OneElementObservable. And OneElementObservable would be a subtype of Observable, because it's more specific in the sense that it adds the guarantee that there will only be one element.


How would it solve issue 564 which was about zipping streams together?

To find out that the old wrong sequenceEqual might emit several Booleans instead of one single Boolean, it would have been sufficient to look at the return type (which would have been Observable instead of OneElementObservable), and there would have been no need to look at the documentation or at the source code. So it would have been easier to spot this error, and therefore it would probably have been found earlier. In general, a OneElementObservable type can prevent misunderstandings.

 
The type would only carry the information until it is composed with an Observable, then it would be lost.

This depends on the composition operator. For instance, OneElementObservable<T1> could have an instance method zipWith<T2, T3, R>(Observable<T2>, Observable<T3>, Func3<T1, T2, T3, R>) which would return a OneElementObservable<R>, because we know that we will only get one element of type T1, so the zipped Observable will also have at most one element. So when zipping together several network calls, you could preserve the information that it's only 1 element, but when merging n network call results, you'd get an Observable emitting n elements, and this n cannot be tracked by the type system any more.

Reply all
Reply to author
Forward
0 new messages