During my introduction to reactive programming, I was shown a giant reactive codebase and told, “Everything is a signal.” Of course, my first question was, “What is a signal?”
In my opinion, this question is the sticking point for many reactive tutorials and explanations. Is a pipeline a good analogy for a signal? Maybe an electrical circuit? Perhaps we need to be less metaphorical and more technical; some tutorials take this approach and say, “A signal is a data stream.” Okay, but that doesn’t add any new information…
Before going any farther, I should clarify that I will be using the term “signal” to describe reactive programming’s lead actor. Every language and framework calls it something different, with other common choices being “observable” or “stream.”
The resource that helped me conceptualize the idea of a signal was a marble diagram. These diagrams have made reactive documentation much more approachable than the predating tables and tables and tables of text. They’re regularly used to depict reactive operators, but I don’t think they are leveraged often enough in explaining the basics.
Signals and Marble Diagrams
Reactive marble diagrams come in three parts: input, operation, and output.
The input and output are signals, and the form of the operation can vary from operator to operator. Let’s start by focusing on a single signal.
The horizontal arrow is a simple timeline. It extends forever to the right regardless of what the signal does.
Circles, or marbles, along this line represent values emitted by the signal. A signal may never emit a value, it may emit infinitely many values, or it may emit a number of values somewhere in-between.
What kind of values might be emitted? Pretty much anything…limited only by your language, framework, and domain. For example, a signal might emit simple data types, input/output events, or even other signals.
The other two symbols that regularly appear along our infinite timeline are an X or a vertical bar. The X represents an error. The vertical bar is the signal’s completion. No values can be emitted past completion. Note that a signal need not ever complete or err, which allows for infinite event emissions.
These three things make up the components of a simple signal. Every signal conforms to this interface in some analogous way: an onNext(), onError(), and onComplete() that are represented by circles, Xs, and vertical bars respectively.
The remainder of most marble diagrams is simply an operation that specifies a function from some input signal to an output signal. There are cases with some nuance, including things like time windows, but understanding the basic concept of a signal and being able to interpret the applied operation is plenty to get you started with reactive programming.
From here, working in a reactive codebase comes down to practicing appropriate ways to manipulate signals.
Let’s look at an example of one marble diagram. Below is a diagram for a merge operator.
Notice that there are two input signals in this diagram. The merge operator can accept more than two input signals, but most require that all input signals are of the same type.
The colors help the reader visualize which input events correspond to each output event. Here, we can see that the merge operator is merging all of the input events into a single signal.
One final thing to note with the merge operator is the order of completion events. The merged output signal only completes once all of the input signals complete. On the other hand, an error would occur in the output signal as soon as any input signal has an error.
I’d like to conclude by giving a shoutout to the ReactiveX site for its documentation. The site includes marble diagrams and explanations of many different operators including language specific information. It also has more explanation of the basics of the reactive paradigm and some more advanced topics.
I really enjoy that some of their marble diagrams are interactive, allowing you to drag and drop events, errors, and completions to experiment with how the operator works.