Tom MacWright

Restrictive programming

Often opinionated code is really restrictive code, and that can be a good thing. ‘Restrictive’ has a negative connotation in English, which is unfortunate - ‘constrained’ also works.

I think this is true in languages and frameworks. The gist being:

  • A popular design choice elsewhere is actually a mistake, so it isn’t included
  • A corner case is a wicked problem, so we would rather leave it uncovered than accomodate it.
  • A shortcut in other systems can be expressed in more words but fewer total words.

A few examples:

React allows for both local state modified by imperative calls (setState), and global state (props). Cycle.js and Deku ban local state, requiring all state to be global. You can use React without local state, using tools like Redux, but aren’t required to do it. Removing even the possibility makes certain corner cases much more difficult to handle, but also removes an ambiguity that requires design patterns and can make component use more uncertain.

Rust removes the concept of null, often called a billion dollar mistake by its creator but an idea that spread to nearly every other language.

React also includes forceUpdate(), a function that the documentation advises:

Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your component “pure” and your application much simpler and more efficient.

But novice developers often use forceUpdate to write code similar to non-Reactive code, where changes in rendering aren’t derived from changes in data. Once this pattern is discovered by their coworkers, they start using something like eslint-plugin-react to ban features from React that can be appropriate in corner cases but are usually wrong.

Haskell, a famous functional language, is purely functional. Most languages are multi-paradigm, like Python or JavaScript: you can use for loops to iterate over data, or you can use recursion. The latter is mathematically beautiful and, in some sense, simpler, but the former is, in many cases, faster and more understandable to a larger set of programmers. Haskell only allows functional code.

  • Languages and programs are often overbuilt. Most languages have multiple ways to express the same idea, and struggle to consolidate on the good ones, whether by software that bans features (eslint) or conventions (PEP). Rust famously has eliminated features early in its development.
  • The Sapir-Whorf hypothesis is still persuasive, and Paul Graham’s thoughts about language hierarchy make sense.
  • ‘eslint JavaScript’ is its own language, the restrictive subset of JavaScript that no standards body can pass.
  • ‘Back doors’ like mutability in many languages or dangerouslySetInnerHTML in React are extremely fraught: they solve certain problems, but if they’re too convenient, serve as a crutch that lets people skip the learning curve but end up with dangerous code. Even Rust has an unsafe mode that lets you do scary things. These features are all covered with ‘danger’ signs, but too often those are ignored and the assumption is that if it compiles then it’s okay.