The React team at Facebook has been using terminology from functional programming, such as ‘functional components’ and ‘higher order components’ to describe certain features of React. This has lead a whole generation of JavaScript programmers to believe they are doing functional programming when they write code using React. These programmers have been misled about what functional programming is all about.
To prove this, let’s first examine what functional programming is, what its key tenets are, and then we’ll see how much of it React aligns to.
What is functional programming?
Functional programming (FP) is a programming paradigm based on computation as evaluation, as opposed to imperative programming, in which computation causes state change [1].
What is cool about functional programming?
- Verification: It is much more straightforward to reason about functional programs than it is about imperative programs. This gives us increased confidence in the correctness of programs.
- Parallelism: Since expressions in FP have no side-effects, it is natural to use parallel evaluation: the values of independent subexpressions may be determined simultaneously, without fear of interference or conflict, and the final result is not affected by evaluation order.
- Data-centric computation: Operations act on compound data structures as whole, rather than via item-by-item processing. That means you get to use statements such as this example which calculates the sum of the first 10 prime numbers:
const sum = [...Array(100)].map((_, i) => i).filter(n => is_prime(n)).slice(0, 10).reduce((a, b) => a + b, 0);
Of these benefits, ease of verification is the most important to JavaScript programmers. Parallelism is not a benefit because JavaScript doesn’t support it. Acting on compound data structures as a whole is supported but since JavaScript doesn’t support lazy evaluation, it doesn’t work as well as in languages such as Haskell or Rust.
Key principles
- Pure functions: Functional programs are constructed using only pure functions, meaning functions that have no side effects. A function has a side effect if it does anything other than simply return a result. For example, modifying global state, printing a message to the console, and mutating the DOM are all side effects. Side effects make reasoning about program behavior more difficult.
- Referential transparency: Can a specific call to your function be replaced with its corresponding return value without changing the program’s behavior? This is a requirement in order to be functional. This is called referential transparency.
Here’s a quick test (assuming TypeScript): is the return type of your function void
? If so you are using the function for its side effects. That’s not allowed in functional programming!
Another test: Does your function register event handlers to a DOM element? If so the function is not referentially transparent, and that’s not allowed in functional programming!
A pure function cannot do I/O. It cannot update the screen because then it is doing something besides returning a value. It cannot read any input either because it is going to potentially return a different value each time. That’s not allowed because in FP a function should always return the same value, given the same arguments. For more info see Wikipedia article about pure functions.
Functional programming in practice
It is well and good to talk about the benefits of pure functions, and how the absence of side effects makes your program easier to verify, but at the end of the day if your program doesn’t take any input nor display anything on the screen, you’re not going to get paid!
So how do you do I/O? We allow some of our functions to be impure, and we do so reluctantly. We’re going to need a way to seclude the impure functions. It turns out that FP languages such as Haskell [3] have a system for dealing with functions that have side-effects, that neatly separates the part of the program that is pure and the part of the program that is impure (and does all the dirty work like updating the screen). Impure parts of the program are fenced off from the pure parts. Pure functions cannot call impure functions.
With pure and impure parts separated, we can still reason about our mostly pure program (which is the majority of our code) and take advantage of all the things that purity offers while still communicating with the outside world. The important part here is keeping portions of your code that has side-effects separate — and minimizing the amount of such code.
Functional programmers often speak of implementing programs with a pure core and a thin layer on the outside that handles effects [2].
If all of your functions are impure then your code is not really FP.
Functional programming and React
Once you understand the rules of functional programming it is easy to see why React is not functional. The first version of functional components in React was indeed functional. Here’s an example:
function SayHello(props: { name: string }): JSX.Element {
return <div>Hello, {props.name}</div>;
}
The above function is pure and it is referentially transparent.
But then they added hooks and useState
and useEffect
and so on, which made the functions impure. The problem with hooks is that they “hook into” React state and lifecycle features, which means it is modifying global state.
- From React documentation: “The Effect Hook lets you perform side effects in function components.” But functions in FP are supposed to be pure, which means they are supposed to be free of side effects!
- From React useState documentation: “This is a way to ‘preserve’ some values between the function calls”. That’s not allowed in functional programming! Functions in FP functions are stateless, and they are only allowed to look at their parameters in order to calculate the return value.
It should be clear by now that React breaks the rules of functional programming in fundamental ways.
As mentioned earlier, in practice, some of the functions in your FP program are going to have side effects. That’s expected. But you have to fence such impure functions off from your pure functions. What if all your functions are impure? Well, at that point you have to stop pretending you’re doing functional programming!
Does Algebraic Effects provide salvation?
Some React folk bandy about a construct known as ‘algebraic effects’ as if it is some sort of trump card that lets you ignore the “inconvenient” rules of functional programming while still benefiting from its cachet. What is this magic? Does it turn impure code pure? It does not.
Algebraic effects represent impure behavior in a functional programming language, such as input and output, exceptions, nondeterminism etc. all treated in a generic way [4]. It does not sanctify impure code. You still have to fence off impure parts of the program from the pure parts, and minimize the amount of impure code.
Should React have stayed with classes?
Classes and OOP are well-understood concepts and work well for the vast majority of cases. Functional components and hooks are great too. Hooks allow you to attach reusable behavior to components in a way that’s more elegant than previous patterns such as mixins, higher-order components and render props. That’s well and good, but confusing a whole generation of programmers into thinking they are doing functional programming when they are not, is a problem. Facebook should not lean on the cachet of functional programming when promoting React.
References
[1] Principles of Functional Programming — CMU
[2] Functional Programming in Scala — Chiusano
[3] Learn You a Haskell for Great Good!
[4] Program Equivalence for Algebraic Effects via Modalities