Chapter 14 Why reactivity?

14.1 Introduction

The initial impression of Shiny is often that it’s “magic”. This is great when you get started because you can make simple apps very very quickly. But magic in software usually leads to disillusionment. Without a solid mental model to reason with, it’s extremely difficult to predict how the software will act when you venture beyond the borders of its demos and examples. And when things don’t go the way you expect, debugging is almost impossible. Even experienced R users can have trouble getting their heads around reactive programming, and those with deep experience in software engineering may feel uncomfortable with so much “magic”.

Fortunately shiny is “good” magic. As Tom Dale said of his Ember.js JavaScript framework: “We do a lot of magic, but it’s good magic, which means it decomposes into sane primitives.” This is the quality that the Shiny team aspires to for Shiny, especially when it comes to reactive programming. When you peel back the layers of reactive programming, you won’t find a pile of heuristics, special cases, and hacks; instead you’ll find, a clever, but ultimately fairly straightforward mechanism. Once you’ve formed an accurate mental model, you’ll see that there’s nothing up Shiny’s sleeves: the magic comes from simple concepts combined in consistent ways.

In this part of the book, you’ll dive into the details of reactivity, learning why reactivity is necessary, how it works underneath the covers, and how you might use the atoms of reactivity to create your own building blocks.

14.2 Why reactive programming?

Reactive programming is a style of programming that emphasizes values that change over time, and calculations and actions that depend on those values. This is important for Shiny apps because they’re interactive: users change input controls (dragging sliders, typing in textboxes, and checking checkboxes) which causes logic to run on the server (reading CSVs, subsetting data, and fitting models) ultimately resulting in outputs updating (plots replotting, tables updating).

For Shiny apps to be useful, we need two things:

  • Expressions and outputs should update whenever one of there input values changes. This ensures that input and output stay in sync.

  • Expressions and outputs should update only when one of their inputs changes. This ensures that apps respond quickly to user input, doing the minimal amount.

It’s relatively easy to satisify one of the two conditions, but much harder to satisfy both. To see why, and to see how we might attack the basic problem with other styles of programming we’ll use a very simple example, eliminating the additional complexity of a web app, and focussing on the underlying code.

14.2.1 Why can’t you use variables?

In one sense, you already know how to handle “values that change over time”: they’re called “variables”. Variables in R represent values and they can change over time, but they’re not designed to help you when they change.

Take this simple example of converting a temperature from Celsius to Fahrentheit:

So far so good: the temp_c variable has the value 10, the temp_f variable has the value 75.6, and we can change temp_c:

But changing temp_c does not affect temp_f:

Variables can change over time, but they never change automatically.

14.2.2 What about functions?

You could instead attack this problem with a function:

(This is a slightly weird function because it doesn’t have any arguments, but instead accesses temp_c from its enclosing environment, but it’s perfectly valid R code.)

This solves the first problem that reactivity is trying to solve: whenever you access temp_f() you get the latest computation:

It doesn’t, however, solve the second problem of trying to do as little computation as possible. Every time you call temp_f() it recomputes:

That isn’t a problem in this trivial example, but minimising recomputation is a substantial challenge in real apps.

14.2.3 Event-driven programming

Since neither variables nor functions work, we need to create something new. In previous decades, there wouldn’t have been any question about what we should create: we’d implement something to support event-driven programming. Event-driven programming is an appealingly simple paradigm: you register callback functions to be executed in response to events (e.g. a mouse click, or a textbox’s value being changed).

We could implement a very simple event-driven toolkit using R6. A DynamicValue has three important methods: get() and set() to access and change the underlying value, and onUpdate() lets you register code to run every time the value is modified. If you’re not familiar with R6, don’t worry about it, and instead focus on the interface as shown in the next examples.

So if Shiny had been invented five years earlier, we might’ve have written something like this:

Now temp_c is a dynamic value that uses <<- to automatically update temp_f whenever it changes.

Unfortunately, this kind of programming is simple, not easy! As your application adds more features, it becomes very difficult to keep track of what inputs affect what calculations, and what calculations affect each other, and what input and calculations affect what outputs and actions. Before long, you start to trade off correctness (just update everything whenever anything changes) against performance (try to update only the necessary parts, and pray you didn’t miss any edge cases) because it’s so difficult to do both.

14.2.4 Reactive programming

Reactive programming elegantly solves both problems by combining features of the solutions above. Now we can show you some real Shiny code, using a special Shiny mode, consoleReactive(TRUE), enabling reactivity in the console so you that you can experiment with it directly, outside of an app. This mode is isn’t enabled by default because it makes a certain class of bug harder to spot in an app, and it’s primary benefit is to help you understand reactivity.

As with event-driven programming, we need some way to indicate that we have a special special type of variable, a reactive value25, created with shiny::reactiveVal(). This creates a single reactive value that has a special syntax for getting and setting its value. To get the value, you call it like a function; to set the value, you call it with a value.

Now we can create a reactive expression that depends on this value. As you’ve seen previously, a reactive expression automatically tracks all of its dependencies:

Later, if temp_c changes, temp_f() will be up to date:

Note that the conversion only happens if we request the value of temp_f() (unlike the event-driven approach), and the computation happens only once (unlike the functional approach). A reactive expression caches the result of the last call, and will only recompute if one of the inputs changes.

Together these properties ensure that Shiny does as little work as possible, making your app as efficient as possible.

14.3 A brief history of reactive programming

You can see the genesis of reactive programming over 40 years ago in VisiCalc, the first spreadsheet:

I imagined a magic blackboard that if you erased one number and wrote a new thing in, all of the other numbers would automatically change, like word processing with numbers. — Dan Bricklin

Spreadsheets are closely related to reactive programming: you declare the relationship between cells (using formulas), and when one cell changes, all of its dependencies automatically update. So the chances are you’ve already done a bunch of reactive programming without knowing it!

While the ideas of reactivity have been around for a long time, it wasn’t until 1997 before they were seriously studied as a research topic within computer science. Research in reactive programming was kicked off by FRAN (Elliott and Hudak 1997), functional reactive animation, a novel system for incorporating changes over time and user input into a functional programming language. This spawned a rich literature (Bainomugisha et al. 2013), but it took some time until it affected how people program.

In the 2010s, reactive programming roared into the programming mainstream via the blisteringly fast-paced world of JavaScript UI frameworks. Pioneering frameworks like Knockout, Ember, and (Joe Cheng’s personal inspiration) Meteor demonstrated that reactive programming could make UI programming dramatically easier. Within a few short years, reactive programming has come to dominate UI programming on the web, with hugely popular frameworks like React, Vue.js, and Angular which are either inherently reactive or designed to work hand-in-hand with reactive backends.

It’s worth bearing in mind that “reactive programming” is a fairly general term. While all reactive programming libraries, frameworks, and languages are broadly about writing programs that respond to changing values, they vary enormously in their terminology, designs, and implementations. In this book, whenever we refer to “reactive programming”, we are referring specifically to reactive programming as implemented in Shiny. Conversely, if you read any material about reactive programming that isn’t specifically about Shiny, it’s unlikely that those concepts or even terminology will be relevant to writing Shiny apps. For readers who do have some experience with other reactive programming frameworks, Shiny’s approach is similar to Meteor and MobX, and very different than the ReactiveX family or anything that labels itself Functional Reactive Programming.

References

Bainomugisha, Engineer, Andoni Lombide Carreton, Tom van Cutsem, Stijn Mostinckx, and Wolfgang de Meuter. 2013. “A Survey on Reactive Programming.” ACM Computing Surveys (CSUR) 45 (4). ACM:52. http://soft.vub.ac.be/Publications/2012/vub-soft-tr-12-13.pdf.

Elliott, Conal, and Paul Hudak. 1997. “Functional Reactive Animation.” In International Conference on Functional Programming. http://conal.net/papers/icfp97/.


  1. If you’ve programmed in languages like rust or clojure this might look familiar: a reactive value is very similar to a mutable cell.