The Taxonomy of Reactive Programming

Victor Savkin
Angular
Published in
9 min readJul 13, 2016

--

Victor Savkin is a co-founder of nrwl.io, providing Angular consulting to enterprise teams. He was previously on the Angular core team at Google, and built the dependency injection, change detection, forms, and router modules.

We all build user interfaces using some form of reactive programming. A new todo was added? We need to render it on the screen. Someone changed the todo’s title? We need to update the text element in the DOM. And there are dozens of libraries out there that help us do that. They are similar in some ways and different in others.

In this article I will introduce four independent dimensions of reactive programming: events and state, deriving and executing, reified and transparent, self observation and external observation. And I will explain how using these dimensions, as well as the vocabulary that comes with them, we can make our discussions about different libraries and approaches more objective and concise.

The examples in the article are written using both Angular 2 and RxJS. By including examples of each, it will become clear that even when you compare a batteries-included framework with a reusable library, we can still talk about them using the same terminology. The conversation can still be meaningful.

Let’s get started.

Events and State

We often talk about events or event streams when discussing reactivity. Event streams are an important category of reactive objects, but we should not forget about the second important category–the state. Let’s compare their properties.

Events are discrete and cannot be skipped. Every single event matters, including the order in which the events are emitted. The “most recent event” is not a special thing we care about. Finally, very rarely are events directly displayed to the user.

The state, on the other hand, is continuous, i.e., it is defined at any point in time. We usually do not care about how many times it gets updated–only the most recent value matters. The state is often displayed or has a meaningful serialization form.

Take a shopping cart as an example. We can use mouse click events to change the content of the shopping cart. Clicking on a plus button increases the number of items, and clicking on a minus decreases it. The number of clicks matters. And we cannot skip any of them, as it would change the content of the shopping cart. We will never have to examine the last click event or display it to the user.

The totals field, on the other hand, is the state. We only care about its latest value, which reflects the content of the shopping cart, not about how many times the total was updated.

Definition

Event streams are sequences of values produced over a period of time. And the state is a single value that varies over time.

Let’s look at an example.

Here we have the moves observable, which is a sequence of events. Every time the user moves the mouse, the observable will emit an event. We use moves to create the position subject, which is the state. The position’s value property returns the current position of the mouse. In addition to illustrating what events and the state are, this example also shows that the state is often created from a sequence of events.

To show that the event-state dichotomy is not just an RxJS artifact, let’s look at this Angular example.

In this example, the name input of the employee component is the state, which is derived from the employees property of the company component. Note that we only care about the most recent value of name, i.e., skipping an intermediate value of name won’t affect anything. Contrast it with the selected event sequence, where every single emitted value matters, including the order in which they are emitted.

Summing Up

There are two categories of objects we talk about in the context of reactive programming: event streams and the state. Event streams are discrete sequences of values spread over time, which are not displayed to the user. The state is a continuous value that varies over time, which is often displayed to the user or has a meaningful serialization form.

Deriving and Executing

Say we have an RxJS observable of mouse clicks.

What can we do with this observable? The first thing that comes to mind is printing the coordinates.

This is useful, but not much different from using a callback.

The real value of having the clicks observable is the ability to derive new observables from it.

Note, the only thing the map operation produces is a new observable. There is nothing else in the program that is affected by it. In other words, this program does not have side effects.

Now, let’s go a little bit further and use a few RxJS operators to create a new observable, where every element has the to and from coordinates.

Once again, we only specified the relationships between observables. That is what derivation is all about–it is about specifying how different entities relate to each other. The result is an observable or a set of observables representing the computation your program will perform. This is the representation of the computation, not the computation itself. Nothing is actually displayed to the user.

To display something to the user, or, in other words, to execute side effects, we need to call forEach.

RxJS isn’t the only library that makes derivation and executing side effects distinct operations. Angular does it too.

The name property is derived from the ceo property, and ngOnChanges is used to execute side effects.

Hot and Cold. What About Promises?

Interestingly, because RxJS observables are cold, the library has to separate the two operations. It has to provide forEach and have some notion of subscription. Otherwise, no side effects will ever be executed.

Promises, on the other hand, are hot. In other words, once the producer promise has a resolved value, it will notify all the derived promises. Because of that promises can get away with having the single ‘then’ operator, which can be used for both derivation and executing side effects.

The “hot/cold” topic is nuanced and affects not just side effects and subscriptions, but also cancellation. Read more about it in this excellent article by Ben Lesh.

Summing Up

There are two ways of dealing with time-varied variables: deriving new time-varied variables from them, and executing side effects when the values change. Separating the two makes the code more composable and easier to refactor.

Reified and Transparent

Let’s look at this example.

Here we have two subjects: ceo and name, which allow us to observe the changes of the ceo and name values. We can call this type of reactive programming reified because we have access to the concrete objects representing the act of observation. And having these concrete objects is powerful because we can manipulate them, pass them around, and compose them.

The difference between name.value and name can be subtle at first glance, but it is crucial: name.value is the current value of the name state, but name itself is something else. It represents the value changing over time.

Contrast it with a similar Angular example.

Angular does not provide any object representing name changing over time–we only get the current value. We can call this type of reactive programming transparent because the developer only interacts with the most recent value. The act of observation is hidden in the framework.

Composition

Composition works very differently in transparent and reified reactive programming.

When using reified reactive programming, we have to use special operators to create new variables out of existing ones, as follows:

When using transparent reactive programming, we can use ‘plain’ JavaScript.

When it comes to dealing with the state, we can use both the types of reactive programming effectively. Transparent reactive programming is a lot simpler and more performant, and the reified one is more flexible.

We cannot use both the types when dealing with events. To understand why we need to look at how the two types of reactive programming handle time.

Implicit and Explicit Time

When using transparent reactive programming, we cannot deal with time explicitly. Moreover, the notion of time is not really defined. We, of course, understand that the name property will change as time goes by, but we are not given any handles to make interesting decisions based on it.

That’s where reified reactive programming shines. It makes time explicit and provides special operators to use it in composition. For instance, we can delay the update of the name subject by half a second like this.

Using time when deriving the state is rarely practical, but using it when dealing with events is common (e.g., debouncing). For this reason, having access to only the latest value (as you would with a transparent paradigm) is often insufficient when dealing with events.

Summing Up

Transparent reactive programming is simpler because we can use plain JavaScript to compose multiple variables that vary over time. It, however, provides only the latest value of a variable and does not give us explicit ways of working with time. Consequently, it is useful for the state derivation, but not for manipulating events.

Self and External Observation

Definition

External observation is when the observed object is not aware of its observation. Instead, some external entity (e.g., Angular) knows when the object changes. Self observation is when the object itself knows of and manages its own observation.

Again, let’s look at this example.

In this example we are deriving the fullName property of the employee component from the ceo property of the company component. In other words, we are telling Angular to observe the ceo object, and any time its first or last name changes, to update the employee component. Interestingly, the ceo object does not have to do anything to be observed. It does not have to be decorated or created in some special way.

This is because Angular uses external observation. Angular computes the expression in the template and remembers the result. The next time it computes the same expression, it will compare the new and the old values. And if the value has changed, the framework will update the employee component.

External observation has a lot of advantages. First, it enables a great deal of flexibility. You can use any object implementing the {firstName: string, lastName: string} interface. It can be json fetched from the backend or an ImmutableJS record. On top of that, external observation enforces stronger order guarantees and avoids the problem of cascading updates. The downside, though, is that to be observed the code has to run in the context of the framework. That’s why Angular 1 has scope.apply, and Angular 2 requires the use of zone.js.

Contrast it with self observation, where the object is aware of its own changes, and notifies whoever is listening itself. Libraries like MobX or Knockout employ this approach.

Summary

In this article, I set out to introduce four dimensions crucial for talking about reactive programming. My hope is that we can become more concise and objective when comparing different libraries and approaches.

Now, when you see a new library, ask yourself the following four questions:

  • Does it deal with events or state?
  • Does it derive values or execute side effects?
  • Does it use transparent or reified reactive programming?
  • Does it use external or self observation?

For instance, when discussing Angular, we can say that it uses transparent reactive programming with external observation for the state derivation. And it uses reified reactive programming with self observation for dealing with events. And hopefully, you understand why we made this choice. Another example is MobX, which uses transparent reactive programming with self observation, and it deals with state. Or consider Cycle.js, which uses reified reactive programming with self observation.

Do we now know absolutely everything about reactive programming? Of course not! It is naive to think the whole field of research can be covered in one article. For instance, I haven’t touched on “push and pull”, first and higher order FRP, continuous FRP, the Observable/Iterable duality, back pressure, sampling, and many other interesting and important topics. These may be topics of future blog posts, so stay tuned.

Victor Savkin is a co-founder of Nrwl. We help companies develop like Google since 2016. We provide consulting, engineering and tools.

If you liked this, click the 👏 below so other people will see this here on Medium. Follow @victorsavkin to read more about monorepos, Nx, Angular, and React.

--

--

Nrwlio co-founder, Xoogler, Xangular. Work on dev tools for TS/JS. @NxDevTools and Nx Cloud architect. Calligraphy and philosophy enthusiast. Stoic.