5

I recently came across the ReactiveX pattern for asynchronous data-flows. I studied the information provided there and also watched this talk by a Netflix engineer on how they used Observables to redesign their web API architecture.

ReactiveX certainly seems like a clean and powerful abstraction, but I don't see the difference between an Observable<T> and a Iterable<Future<T>>. In the first case, I could do something as follows:

Observable<T> observable = getData()
observable.subscribe(onSuccess, onError, onCompletion)

And in the second case, I would do

Iterable<Future<T>> futures = getData()
futures.forEach(future => future.onSuccess(onSuccess), future.onError(onError))

where onSuccess, onError, and onCompletion are some functions I have defined elsewhere.

What advantages does the first example have over the second?

P.S. This is just out of curiosity. I currently use Python (and JavaScript) in my job, so I won't be using the ReactiveX paradigm anytime soon.

gardenhead
  • 4,719
  • 2
  • 19
  • 24
  • 1
    From your example, it would appear that `Future` is a kind of `Observable`. So really the difference seems to be subscribing to a single observable versus subscribing to a collection of observables. – Ben Cottrell May 07 '16 at 15:33
  • 1
    Hi. Check out official's page example. [Reactive Programming](http://reactivex.io/intro.html). *Reactive Progamming* block. It says what @Giorgio is explaining – Laiv May 07 '16 at 17:20

1 Answers1

4

Your examples are not semantically equivalent:

Observable<T> observable = getData()
observable.subscribe(onSuccess, onError, onCompletion)

will execute the onSuccess, onError procedures each time a new element is received.

On the other hand, with

Iterable<Future<T>> futures = getData()
futures.forEach(future => future.onSuccess(onSuccess), future.onError(onError))

the iterable delivers all the futures, and you register callbacks on each future as you get it from the iterable. But, at this point, the futures may still be running. Later, these futures may complete in any order, not necessarily in the order in which they were provided by the iterable.

Also, the first solution can received a possibly unlimited stream of data without blocking: each time a new element arrives, a callback is invoked. The second solution can't: it only makes sense if you want to start a finite number of asynchronous computations.

To have a better intuition, take a look at the table on this page or this one (thanks to @Laiv for the link, I was not able to find it back).

  • In your first solution you have multiple values that are asynchronously pushed to your program (bottom right of the table).

  • In your second solution you have multiple value that are synchronously pulled by your program. In turn, each one of these values, is a computation that asynchronously pushes a single value.

An example of what you can do with the first solution is the following. Suppose that you have an observable representing all GUI events in an application:

Observable<Event> events = ...

Now if you want to listen only on the events of a given button that is, say, identified by its label "HelpButton", you can write the observable:

Observable<Event> helpButtonEvents =
    events.filter(e => "HelpButton".equals(e.getName()))

helpButtonEvents.subscribe(onHelpInvoked, ..., ...)

It should be clear that you cannot use an iterable of future here, because you do not know how many GUI events you will get and therefore you would need to have an iterator polling on events forever.

Giorgio
  • 19,486
  • 16
  • 84
  • 135
  • Yes, I've seen that chart, and I like it a lot. But nowhere in that chart do we see `Iterable>`. Only `Iterable` and `Future`. So I don't believe it answers my question. Regarding: "In your second solution you have multiple value that are synchronously pulled by your program." Sure they are synchronously pulled, but they are Futures, so they are pulled immediately. What's important is that the values the Futures contain are themselves asynchronously pushed. – gardenhead May 07 '16 at 18:47
  • "But nowhere in that chart do we see Iterable>": It is not there because Iterable> is a composite of Iterable (pull of multiple values) where each value is of type S = Future (push of a single value). So you first pull several future-values, and then its future pushes its own value. – Giorgio May 07 '16 at 19:04
  • "What's important is that the values the Futures contain are themselves asynchronously pushed.": With this solution you cannot receive an infinite sequence of values asynchronously. With the first you can. – Giorgio May 07 '16 at 19:05
  • OK, I agree that you cannot receive an infinite number of events with an `Iterable`. More accurately, you must set the number of events before you can know how many events will actually be triggered. That's an important difference, but I wonder if there's any others. – gardenhead May 07 '16 at 19:23
  • @gardenhead: Another difference is that the order in which your futures are going to respond may be different from the order in which they were created. Is this what you want? – Giorgio May 07 '16 at 20:01
  • @gardenhead: Also, you cannot map, filter or fold on your sequence of futures in the same way as you would do on an observable. – Giorgio May 07 '16 at 20:02
  • But neither Observables or Iterables have an order, so I don't think that point matters. Sure, the order will be different, but in these situations the order shouldn't matter anyway. I believe you can do typical higher-order functions on an `Iterable>`, it's just more cumbersome. – gardenhead May 07 '16 at 21:56
  • "it's just more cumbersome": Yes, definitely cumbersome! Imagine you want a sequence of mapped values that respects the order in which the original futures have completed. How would you do that? – Giorgio May 08 '16 at 07:48