A Functional Approach to Building User Interfaces
11 min read

A Functional Approach to Building User Interfaces

React.js[^n] has been consistently rising in popularity since its release, and with it, so too has the idea of applying functional programming concepts to building web user interfaces. Many of these concepts have become pretty popular talking points in this context, such as the benefits of avoiding shared mutable state. However, I find there are two slightly less popular, though certainly powerful concepts in particular which are worth discussing: function composition, and higher-order functions.

I discuss these concepts in the context of React to help frame the trade-offs when compared to more conventional approaches, because I think React applies these ideas very well in practice. That said, I don't intend to convince you to use React with this article. Instead, I intend to convince you that the concepts we discuss here have important implications in regards to building web user interfaces, and I hope that you find insight here that you can bring to whichever stack you prefer working with.

Function Composition

I suspect that I don't need to convince you of the idea of composition itself; this is something that has been a part of program architecture since the introduction of subroutines in Pascal, and certainly you can see its implementation in many pieces of conventional web application architecture. There's even a classic analogy in comparing a knitted castle to a Lego castle. Which do you suppose is easier to build? Which do you suppose is easier to change? Unfortunately, changing certain features of a knitted castle might mean unwinding much of what you had no intention of changing.

Function composition, in its application to constructing web user interfaces, however, is a relatively new idea, and one that not everyone has bought into. In particular, I think it's really valuable to contrast the way in which conventional template construction solves the problem of structuring a DOM representation to the way in which React applies function composition to solve the same problem. This comparison will yield insight into how function composition can help reduce the complexity of constructing the application, thereby making the code easier to reason about, more predictable, and thus ultimately more reliable.

Let's start by addressing what makes conventional template and partial template composition underpowered. Partial templates are usually small HTML files that contain a fragment of your document. These fragments often also carry an implicit expectation that they will be injected into the right parent context, set by the fact that they contain references to a globally shared namespace that gets attached once at the time of rendering the parent template.

<div class="person">
  <h2>{{first_name}} {{last_name}}</h2>
  <div class="phone">{{phone}}</div>
  <div class="email">
    <a href="mailto:{{email}}">{{email}}</a>
  </div>
  <div class="since">User since {{member_since}}</div>
</div>

Consider the partial template example above, which I borrowed from a tutorial on partial templating with Handlebars.js.[^n] This partial expects that it will be registered into a parent template, and that when that parent template is rendered, the context with which it is rendered will include the variables referenced here. This has important implications in regards to the complexity of the system; we can't look at this partial template file and reason about the state of the system at the time this partial is rendering. That means it's extremely hard to predict the possible outcomes of rendering this partial without first reasoning about all of the parent templates into which it is injected, and all of the invocations of rendering those parent templates with various contexts – a task that grows more complicated as the application grows. Additionally, this situation yields the complication that a small change anywhere along this chain of template dependencies can have cascading effects that are often extremely hard to track down. These expectations, without explicit expression of dependency, open the door to situations such as making a change to the parent context, but not realizing its effect on a separate, small piece of your system until it breaks. This is a common symptom of systems in which modules are highly coupled, a trait that conventional wisdom tells us to avoid. This detail should hopefully not come as a surprise, as the expectations to which I keep referring are examples of coupling.

This problem is one that React has solved very cleanly with pure function composition. Note that I'm very intentional about referring to pure functions here. Pure functions, in short, are functions that operate only on their inputs to produce consistent, deterministic output, and do not produce any observable side effects. This constraint is often a given in functional programming languages, but one that we have to be very aware of, and especially disciplined about, in the world of JavaScript.

The above partial template example might be rewritten as a React component as follows.

var Person = React.createClass({
  render: function() {
    return (
      <div className="person">
        <h2>{{this.props.firstName}} {{this.props.lastName}}</h2>
        <div className="phone">{{this.props.phone}}</div>
        <div className="email">
          <a href="mailto:{{this.props.email}}">{{this.props.email}}</a>
        </div>
        <div className="since">User since {{this.props.memberSince}}</div>
      </div>
    );
  }
});

This Person component might then be instantiated like so.

var instance = Person(null, {
  firstName: 'Nick',
  lastName: 'Thompson',
  phone: '123-456-7890',
  email: 'hi@website.com',
  memberSince: new Date()
});

For those of you unfamiliar with React, the component we're examining here is conceptually equivalent to the following simple function definition, assuming that such a function would return some representation of the DOM.

function Person(firstName, lastName, phone, email, memberSince)

Now, apart from the obvious syntactical differences, there are a few key characteristics of this representation that address the complexity issues we covered with conventional template construction. First and foremost, this pure functional interface severs any implicit coupling to the scope in which it is executed; it will execute in its own scope, referencing only the local variables supplied as arguments to the function invocation. We have thus limited the amount of code you need to reason about in order to determine how this component of your system works; no chasing down and reasoning about parent scopes. This small detail has an extremely important consequence: the complexity of this component is contained to the component itself! This level of isolation drastically improves our ability to write predictable, and reliable building blocks for our system.

There's another key characteristic of this functional interface: it's still just JavaScript. This means that, before consuming the variables passed as arguments to our component, we can apply runtime type checking to tame the Wild Wild West that is JavaScript's type system. React offers really easy type checking with propTypes, ensuring that if any of the values passed as props to your component are not of the type you would expect them to be, you will be notified.

var Person = React.createClass({
  propTypes: {
    firstName: React.PropTypes.string,
    lastName: React.PropTypes.string,
    phone: React.PropTypes.string,
    email: React.PropTypes.string,
    memberSince: React.PropTypes.date
  },
  
  render: function() {
    return (
      <div className="person">
        <h2>{{this.props.firstName}} {{this.props.lastName}}</h2>
        <div className="phone">{{this.props.phone}}</div>
        <div className="email">
          <a href="mailto:{{this.props.email}}">{{this.props.email}}</a>
        </div>
        <div className="since">User since {{this.props.memberSince}}</div>
      </div>
    );
  }
});

I'll point out that this is not a complicated addition to your program. A simple if statement will accomplish the same goal.

function Person(firstName, lastName, phone, email, memberSince) {
  if (typeof firstName !== 'string') {
    console.warn('firstName property must be of type string.');
  }
  ...
}

This is a surprisingly powerful method of taming the complexity introduced by a dynamic type system given how simple these kinds of checks are. And the fact that we can apply this assurance at the component level with such ease is a direct consequence of choosing functions as our building blocks.

By now, we've covered much of what makes functions valuable building blocks in a system where composition is a priority, but I feel there's still something to be said for function composition as a design decision.

“The ways in which one can divide up the original problem depend directly on the ways in which one can glue solutions together.”[^n]

Conventional templating solutions tend towards the partials being sort of the end of the line – I've seen very few systems where partials liberally consume other partials. That means you typically end up with only a handful of pieces that, together, are responsible for assembling a representation of the DOM which is deeply hierarchical. Each piece, thus, has a huge responsibility in and of itself, which amplifies all of the complexity issues we discussed previously, because reasoning about each piece's behavior gets harder and harder as that piece's responsibility grows. This is an obvious consequence of a reluctance to use composition hierarchically.

In contrast, function composition is one of the defining design decisions of React. In fact, if you compare the syntax of composing React components to the syntactical definition of function composition, you'll notice they're actually identical. More importantly, though, is the fact that the React components that you write will exhibit the exact same interface as all of the standard DOM components that ship with React. This means that you end up using the same interface to abstract every level of your DOM hierarchy, thereby reducing the overhead of introducing a new abstraction to your interface. This detail very much enables (and practically begs) you to break down your application into small limited-responsibility components, and compose your product therefrom.

From my experience, this empowerment gives you the opportunity to modularize your DOM construction in ways that previously received little attention, which brings me to the application of the higher-order function ideology to DOM composition.

Higher-Order Functions

Whether or not you're aware of it, you're probably very familiar with higher-order functions. If that statement leaves you scratching your head, I'd recommend cracking open Eloquent JavaScript, by Marijn Haverbeke,[^n] for a refresher. Chapter 5 gives a great overview of higher-order functions in the context of JavaScript. I'll also encourage you to read through "Why Functional Programming Matters," by John Hughes.[1] Section 3, "Gluing Functions Together," gives great insight into the power of higher-order functions by example. Now, I'd like to start this section with a quote from each of these resources.

"Higher-order functions allow us to abstract over actions, not just values.”[2]

This is one of my favorite gems from Chapter 5 in Eloquent JavaScript. Rather than repurposing Haverbeke's words to explain it again, however, I'd just like you to keep this quote in mind. In this section, I intend to reapply the ideology of higher-order functions to the DOM, showing how higher-order functions can also allow us to abstract over rendering behaviors, not just actions.

“By modularizing a simple function (sum) as a combination of a “higher-order function” and some simple arguments, we have arrived at a part (foldr) that can be used to write many other functions on lists with no more programming effort.”[1:1]

This conclusion, drawn from Hughes' paper, nicely articulates the power of higher-order functions, and motivates looking differently at simple functions to form powerful abstractions. We'll take a similarly illustrative approach to applying this motivation to the rendering behavior that underlies the DOM.

Let's start by looking at a hometown favorite, the div tag. In the context of rendering, the div can be seen as a function of its children. Indeed, the way the browser will draw the div depends on the children that it contains. For example, suppose the only child of a given div is textual, say, a short string, then, by default, the div will be drawn the full width of its parent, with the text left-aligned and the height of the div set just to contain its children.

Example of div containing short textual content

If, however, that div tag's only child were a longer string, the text would be forced to wrap due to the imposed width of the div, and thus the height of the div would expand to ensure it contains its children.

Example of div containing long textual content

In this simple example lies a very important detail. A simple div tag, by default, has properties which impose rendering behavior upon its children. In this way, even a simple div can be thought of as a higher-order function, and this realization is one we can leverage to create extremely useful building blocks.

Let's extend our simple example for demonstration. Suppose now, that our div tag also has an attribute style="width: 200px; margin: 0 auto;" (or a class name which has a corresponding rule set defining the same properties). We'll rename such a div tag Box. Now we can treat the Box abstraction in the same way that we treated our previous div tag, such as by rendering longform textual content within it. Only this time, the content will be constrained to 200px in width, and centered within the Box's parent container.

Example of our Box abstraction

Now, certainly this is a contrived example, but the insight it yields, in my opinion, is extremely powerful. To call Hughes' quote back to attention, we can now approach building our user interfaces with a keen eye for modularizing common behaviors, to enable assembling complicated interfaces with ease. The types of abstractions that are worthwhile entirely depend on your specific use case, but, to excite you about this concept, here's another example I've encountered that I particularly like.

We'll start again with a simple div tag, though this time we apply the attribute style="position: fixed; left: 0; top: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.1);", and we'll call it Layer. Next, let's revive the Box abstraction from our earlier example, and add an additional style property background-color: white;. Then we'll define a third abstraction, VerticalAlign, which is a composition of two div tags; the first will have an attribute style="display: table; width: 100%; height: 100%;", and the second will have an attribute style="display: table-cell; vertical-align: middle;". Finally, to bring our example full circle with the arguments made previously for function composition, we can conclude with higher-order function Modal, which renders its children in a vertically aligned box of width 200px over a dark, transluscent background, defined as Modal(x) = Layer(VerticalAlign(Box(x))) where VerticalAlign(y) = div(div(y)) assuming the properties described earlier.

Modal abstraction demonstration

Conclusion

React.js is an excellent demonstration of the application of functional programming concepts to building web user interfaces. Right in line with the arguments against shared mutable state, using pure functions and function composition to construct your DOM representations can drastically reduce the complexity that surrounds building user interfaces. Moreover, by taking such an approach to building user interfaces, we enable a whole new class of abstractions in the way we modularize the behaviors that govern the way our applications render.

I hope you find the ideas I've presented here useful, and that you incorporate them into whatever libraries or frameworks your application is built upon. And if I haven't convinced you, well, give React a try.

Thanks to Jonas Gebhardt (@jonasgebhardt) for helping me flesh out the ideas presented here, and for his constructive feedback and editing.


  1. https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf ↩︎ ↩︎

  2. http://eloquentjavascript.net/ ↩︎