Reactive programming
28th June 2020 · Matteo Merola
The first application of this technique is dated back to 1969 when Remy Landau and Rene Pardo came up with LANPAR Technologies1
Reactive programming is based on mainly 3 points:
- Data streams
- Functional programming
- Asynchronous observers
An encompassing definition of Reactive Programming might be the following:
“The essence of functional reactive programming is to specify the dynamic behavior of a value completely at the time of declaration.”
One of the biggest applications of Reactive Programming is the development of Graphical User Interfaces. This particular kind of software applications show in fact a very high level of asynchronous interactivity. Generally, data flows in a streams of operations performed by users. Each action should be reflected in a rapid, flawless feedback on the screen. This feedback must be prompt and clear because it will be feeding the next actions performed by the user.
Reactive programming focuses on:
- performances
- declarative approach
Data streams
If you are able to specify, in a declarative way, the dynamic behavior of something, then you are not bound anymore to a mere input and output flow. You instead can switch to an adoption of a continuous stream of data that flows through a chain of operations declared beforehand and ends up reflected in one or more final values.
This way of working gives you a high degree of freedom and keeps the complexity of dealing with multiple sequences of data points to a very low level. You might be in facts surprised if you give a look at some of the spreadsheets of people with 0 knowledge about programming. Even though they fully ignore some of the concepts we are most familiar with and even if they don’t have (not even remotely sometimes) the discipline that we instead practice on a daily basis, they are able to support their businesses by setting up a few functional blocks in a spreadsheets and work with streams of diverse data.
In the realm of GUIs, and especially in GUIs with high interactivity, you can see everything as a stream: - a button might stream click events - an input field might stream events containing its value everytime said value changes - the window object might stream events containing the mouse x,y position - an http client might stream a list of post objects everytime it calls the /posts
endpoint - etc…
Functional programming
Reactive Programming leverages Functional Programming to be able to fully describe the behavior of a component ahead of time in a declarative way. It is a little bit like writing a function in a spreadsheet cell: functions are used to interact with streamed data and perform actions accordingly.
Asynchronous observers
One of the most powerful aspect of Reactive Programming is probably its ability to support asynchronicity of execution. As a matter of facts, once the behavior is fully described in a declarative way, each value can be tracked asynchonously allowing thus snappier results.
Performances
When you are working in the realm of CPU-bound or memory-bound applications (think about mobile or embedded for instance), performance assumes a much greater role. As data flows into the system, you want to minimize the amount of changes you perform in order to maximize the throughput of the entire system. Reactive Programming is a great way to achieve this goal.
Forward referencing
Back in 1969, when the first spreadsheet was invented, computers were not so powerful and could not affort full repaint most of the times. Forward referencing allowed to keep track of what value depends on what values creating thus a dependency chain. This chain then could be taken into account when evaluating the values by performing a topological order evaluation.
If A <- B, C
(A depends on B and C) and C <- B
(C depends on B), a topological evaluation would result in:
B = eval(), C = eval(B), A = eval(B, C)
B
is evaluated first, C
is evaluated afterwards and lastly A
is evaluated too. Note that by knowing the dependency chain, together with what you also know when to re-evaluate specific values.
Declarative approach
Probably one of the most important aspect of Reactive Programming is the ability to self-document and entirely describe the behavior of a component in a purely declarative way. Have you ever asked yourself why spreadsheets are so well understood by a wide variety of people? When you don’t know how a cell comes up with a specific value, what is the first thing you do?
In a well defined spreadsheet, chances are that in 1 or max 2 steps you are able to figure out the particular reason behind a specific behavior.
This is mainly because each component is defined in a declarative approach with an outcome oriented philosophy.
Implementation in different stacks
Reactive Programming frameworks have been implemented in many technological stacks2, especially in stacks used for building UIs and CPU and battery bound.
- RxJs (especially used in Angular)
- RxJava (especially used in Android)
- RxAndroid
- Rx.NET
- RxScala
- RxClojure
- RxSwift
- RxKotlin
- RxPY
- Rx.rb
Although many component framework on the web leverage some sort of Reactive Programming library, some instead use Reactive Programming by implementing it directly.
Thinking reactive
Reactive Programming is more than a paradigm shift. Its so wide spectrum of applications makes it suitable for big and small software projects keeping a sweet spot for apps with high level of interactivity and that deal with streams of data. But how does it work on a more specific level? How can I get started with it?
The basics
You have to wrap your head around some important notions:
- Subjects
- Observables
- Observers
- Subscriptions
Subjects
A subject represents the value that will be streamed/observed/subscribed to. It also embodies the strategy with which events will be emitted in the stream.
Examples:
- Subject<Post[]> a subject that streams
Post
objects: starts withnull
and does not keep state everytime a new array ofPost
is emitted - BehaviorSubject<Post[]> a subject that streams
Post
objects: starts with a default value and keeps the last emitted array ofPost
objects - etc…
Observables
Everything can be an observable! You can see an observable as a wrapper around the thing you want to observe. In the previous example, you can see Post[]
as an observable (Observable<Post[]>
).
Cool thing you can do with observables??
- filtering: skipping, taking until, taking while, etc…
- creation: emit every X seconds, create from array, etc…
- transformation: mapping, flattening, combining, etc…
Observers
So you’ve got your subject from which you can derive your observable, cool! The last thing you need now is an observer: who will actually be there waiting for new values from the data stream?
Here is where you can also understand another reason why reactive programming is cool: depending on the actual platform you are on, the observers can vary. On the web, your code very like runs on the main thread in an event loop, so your observers have little space to scale. On other platforms, there might be different options for the way observables are observed (e.g. multithreaded, actor-based, etc…)
The observer contains the logic that handles three main events:
- next: fired every time a new value is emitted on the stream
- error: fired when an error occured in the stream or in another stream back up in the dependency chain
- complete: fired once when the stream has completed and will not emit any more values
Subscriptions
Once you start observing a particular stream of observable, the way you perform actions or evaluations is by subscribing observers to observables.
const s = obs.subscribe(val => ..., err => ..., () => ...)
A subscription is useful also because you can decide, at run time (depending on user input), to cancel the subscription and stop reacting to events.
s.unsubscribe()
Note on React
React, although the name might suggest otherwise, it’s not really a clear example of a Reactive Programming component framework. By default, in fact, when some state changes, React will actually re-evaluate every component and reconcile only the bits that should change using a virtual DOM3.
3 React gives you some more tools to reduce the amount of work that has to be done during the reconciliation phase (see useMemo(...)
for example).