Pure rendering in the light of time and state

(it's the assignment-operator's fault)

When we started about half a year ago on finding a suitable technique to build an enterprise scale web application, one of my team mates shouted at least once a day: “The user interface should be expressed using pure functions!” That sounded a bit vague; yet it started a very interesting dive into several techniques to find the ultimate, maintainable, well-performing pure rendering mechanism. This blogs tells the story of that journey from a bird’s eye view.

Last week Guillermo Rauch wrote in an excellent blog post on why the user interface (UI) should be a pure function of the state of the application. That is; the rendering should be based solely on the state, but without changing it. And he is right. Pure user interfaces are easy to reason about, to mock and to test. Simply because a pure UI can be described as:

UI = view(application_state)

A view function transforms the program’s state into a structure that describes the desired user interface. This is one of the core principles behind user interface frameworks such as React. However, once one starts to use this pattern, it appears that it doesn’t come for free. Because once the ‘application_state’ starts to change, the UI becomes stale unless the developer intervenes.

Well, why is that? It’s all the fault of the assignment operator! In mathematics, the equal sign (‘=’) expresses an equation. The left-hand side is an isomorphic equivalence of the right-hand side. That means that it should be possible to read each occurrence of ‘UI’ as a synonym of ‘view(application_state)’. It means that once the ‘application_state’ changes, the ‘UI’ will morph with it.

Yet, in programming, the equal sign denotes assignment. Meaning: “reduce the right hand-side to a value and assign it to the left-hand side.” Superficially, this seems to be the same. But the problem is that assignment operator in reality just creates a one-time copy! So, once the application state changes, the developers need to take care to re-apply the ‘view’ function so that the ‘UI’ is updated as well.

Every application of the assignment operator creates a potentially stale value in the user interface.

This might not sound like a big deal, but actually it is. First, its awkward to state each time that the application state changes that the UI should be updated. In event handlers like the one listed below, it becomes painfully clear that our UI isn’t a pure function of state-over-time, since we need to forcefully push the state to our UI ourselves:

function onDeleteButtonClick (todoItem) { allTodos.remove(todoItem); redrawTodoList();
}

And that is not the only problem. Creating a complete fresh view from the application state is usually expensive for any decently sized (web) app. So in our event handlers we take a best effort guess about which parts of the UI need an update. This clutters code and is error prone. Or we create events on top of our state that notify view components about state changes. This, however, introduces a lot of boilerplate subscription management.


To fix this, we can take two approaches. The first one is to make rendering really cheap. Just re-apply the ‘view’ function at 60 frames per second and no user will ever see stale data. This is how games work. And you can leverage this technique when using the HTML canvas. For DOM based approaches (which feel more native and provide a lot more flexibility in styling, reuse and events compared to ‘raw’ approaches like the HTML canvas) using immutable data + ReactJS is a popular approach. This renders fast and removes boilerplate from the UI, but introduces boilerplate code around state management.

The second way of keeping the UI in sync with state without trashing performance lies in fixing the assignment operator. If the ‘view’ function is applied automatically and efficiently to our data, we would get rid of our boilerplate code, without losing all the practical advantages of the DOM and without doing great concessions on how we manage our application state. In short, I want to pose the following theorem:

User interface code will be of higher quality if we shift from one-time assignment operators to isomorphic relationships between data.

So, can we fix the assignment operator? Can we just write:

UI <= view(application_state)

Instead of making a one-time copy like the ‘=’ operator does? (Where ‘<=’ establishes a relationship between ‘UI’ and the application of ‘view’ to ‘application_state’.) Sadly, we don’t have such an operator in JavaScript.

One might wonder, is it even possible to have an isomorphic equality operator? Short answer: yes. Spreadsheet applications like Microsoft Excel do understand correctly what the equality operator is all about. I strongly believe that it is for that specific reason that Excel is one of the most successful software products of all time. Because in spreadsheets, one can just state:

C100 = SUM(B2:B99) / A1

In Excel, the equal sign establishes a proper relationship, where ‘C100’ will follow any changes that happen in the ‘B’ column or ‘A1’. Awesome, right? The solution to proper programming has been in our midst already for decades!

“Functional Reactive Programming” is the discipline that applies the principle of reacting to data changes to the programming field. However, most existing approaches are a bit cumbersome in the sense that they expresses relationships as streams (which has its uses!). Basically, it is for people way smarter than me. But alas, the direction is good and it does the job. In existing FRP libraries like RxJS or BaconJS, one can express relations between data like:

nrOfLikes = twitterFavoritesStream.combine(faceBookLikesStream, (t, f) => t + f);

But, can we do better than this? Can we leave most of the fluff out and observe expressions instead of streams, like in Excel? Yes, we can! By using a technique called “Transparent Functional Reactive Programming.” The unique selling point of TFRP is that inter-data relationships are managed automatically whenever an expression is evaluated. This technique has already been applied successfully in frameworks like Knockout and Meteorjs.

The stand-alone MobX library is designed to leverage TFRP principles to create isomorphic relationships between expressions and data structures that will stand the test of time. So, in fact:

There is a syntactical equivalent of “value <= expr”,
namely: “value = mobx.computed(() => expr)”.

“No way! I want to try that!”. Well, OK. Glad you asked; just open a terminal in an empty directory and copy-paste the following command to get started (assuming you have nodejs installed):

Open MOBservable in a REPL

There it is! Your JavaScript environment with MobX pre-loaded. Now just type (or copy-paste):

Creating application state that is reactive, and a view function that automatically responds to it

Running these commands yields the following output (where each new line is the view after a state transition):

The views that are generated for the first five state transitions

There are few important things to notice here. First of all, we didn’ t force the output to be updated anywhere. It was just an effect of changing our data. Secondly, the changes were processed atomically; the number of attendees always matched the attendees list. There were no intermediate results rendered (and yes, the rendering was printed synchronously). And last but not least, even changing the “nrOfSeats” caused a re-render, although it is used only indirectly by our ‘view’ function. And it worked for scalars, function expressions and array.

So, that concludes the introduction to my journey to find a pure render function that works in the presence of state changes, without being forced to write tons of boilerplate. It gave birth to the ideas behind MobX; existing ideas, reapplied. It might look like magic at this point, but well, today’s abstractions are tomorrow’s boilerpalte.

In a future blog post I will apply the rendering mechanism as shown in the demo to the ReactJS framework, to show you how to create React components that perform extremely well and automatically respond to data changes. So stay tuned, and take a sneak preview at the JSFiddle demo or the TodoMVC app built using ReactJS and MobX. You might also be interested in the in-depth performance analysis of ReactJS and observables I blogged about earlier.

Feel free to leave a comment or join the discussion on discord.