The most common example of Reactive programming that I've heard about is a spreadsheet program that has computed values.
Personally, I would think about a spreadsheet more as a hybrid between functional and dataflow programming, but I can see how you can think about reactively.
What's asynchronous there or requires streams in that case?
There is an asynchronous stream of user actions. The stream(s) are the values the user inputs into the cells. Everytime the user changes a cell value, that's a new value in the stream.
Couldn't they also be made with event listeners?
Everything could also be made with event listeners. Reactive Programming is still "only" Turing-complete. Everything you can do with Reactive Programming, can also be done with Event Listeners or Continuations or Callbacks or … Event Listeners and Reactive Programming are two ways of modeling asynchrony. Both can model the same things. Proponents of Reactive Programming do not argue that they can model things that others cannot. They argue that Reactive Programming can model (at least some things) better than other approaches.
Streams are a convenient tool for thinking about Reactive Programming, because of one important reason: familiarity.
In Interactive Programming, the single most important concept is iterating over a collection. It's one of the first things one learns when learning to program. The
each method and the
Enumerable mixin in Ruby, the
foreach statement in C♯ and the
IEnumerator pair of interfaces in .NET, the enhanced
for loop and the
Iterator pair of interfaces in Java, the
Traversable trait in Scala, iterators and loops in C++, you name it. Collections and iterating are extremely important, and everybody knows how to do it.
Streams allow you to re-use all that knowledge by framing Reactive Programming in a setting very similar to iterating over a collection, with just two differences: instead of you pulling out an element from the collection when you want, the collection pushes an element at you when it wants. That's it. Everything else stays the same.
In fact, the relationship is much more than just familiarity. There is actually a deep mathematical relationship between the two: Reactive Programming with Streams is the category-theoretical dual of Interactive Programming with Collections. The Subject/Observer Pattern is the category-theoretical dual of the Iterator Pattern.
It was Erik Meijer who discovered this, and he used it to enormous effect in .NET. He mechanically derived the
IObserver pair of interfaces simply by stupidly dualizing
IEnumerator. The fact that they are duals means that you can literally just swap the parameter types and return types of the methods without even having to think about what you are doing.
And the really cool thing is that duals are still closely related in structure, and a lot of things that work for the original thing also work for the dual. In .NET, for example,
IEnumerator form a monad that can be used with LINQ. And it turns out,
IObserver also form a monad, and thus also can be used with LINQ in exactly the same way that
IEnumerator can. In other words, by framing the problem of Reactive Programming in terms of Streams, you can use the exact same tools for Reactive Programming that you are already used to from Interactive Programming. You can literally write the same LINQ query regardless of whether you are iterating over an array or subscribing to an event stream.
Mapping, folding, reducing, flatMapping, filtering, transforming, partitioning, merging, splitting, … Streams is exactly the same as mapping, folding, reducing, flatMapping, filtering, transforming, partitioning, merging, splitting, … collections.
That's the power of Streams. They let you at least think like your are iterating over collections, and with some clever libraries, even write code like you would with collections.