Make Impossible States Impossible

September 10th, 2018 — 4 min read

by tom coe
by tom coe

This is a phrase I first heard from David Khourshid in his talk at React Rally 2017 Infinitely Better UIs with Finite Automata: "Make impossible states impossible" (super great talk by the way, and xstate is awesome, and David is too). Googling around it looks like it's a pretty popular phrase in the Elm community, though I'm not sure who said it first.

To illustrate what this means, let's checkout at a very simple example:

<Alert>Just FYI</Alert>
<Alert success>It worked!</Alert>
<Alert warning>Head's up</Alert>
<Alert danger>Watch out!</Alert>

Rendered example

You may have used or written a component that has this kind of API. It's nice and clean (in case you're unfamiliar, in JSX, a non-assigned prop like that is the same as assigning it to the value "true", so success is the same as success={true}).

Here's where this kind of API falls over. What should I render with this?

<Alert success warning>
	It worked!
</Alert>

Should it blend the colors? Should I choose one? Which do I choose? Or should we just yell at the developer trying to do this because they obviously don't know what they're doing? (tip: that last one is NOT what you should do).

The idea of making impossible states impossible basically means that situations and questions like these should never come up. It means that you design APIs that make a clear distinction between the possible states of a component. This makes the component easier to maintain and to use.

So what do we do with our simple example? Whelp, all these different props represent is the type of alert that should be rendered. So what if instead of simply accepting the prop itself, we accept a type prop?

<AlertBetter>Just FYI</AlertBetter>
<AlertBetter type="success">It worked!</AlertBetter>
<AlertBetter type="warning">Head's up</AlertBetter>
<AlertBetter type="danger">Watch out!</AlertBetter>

Now it's impossible to have more states than one because there are only three valid values for the type prop (well, four if you count undefined)! It's easier to maintain, easier to explain/understand, and harder to mess up. Everyone wins!

Conclusion

There are various ways to do this effectively and converting a boolean value to an enum is only one such mechanism. It can and does get more complicated, but the concept can seriously simplify your component's and application's state. This is why I recommend you give David's talk a watch (here it is again). And give xstate a solid look as well. There's some good ideas in that! Good luck!

P.S. After publishing this, several people noted that this phrase was the title of a talk from Richard Feldman at Elm Conf. There's also a talk by Patrick Stapfer from ReasonML Munich Meetup. You may also be interested to check out a similar blog post by @stereobooster.

P.S.P.S. Further revelation about the origin of a similar phrase:

Yaron (Ron) Minsky avatar
Yaron (Ron) Minsky @yminsky
@rtfeldman @axiologic @elmlang I did coin the term "make illegal states unrepresentable", but the idea is of course much older. The phrase "Minsky compliant" surely gives me too much credit, but at least it sounds more positive than the concept of the "Minsky moment".

Bonus:

Here's a part from my section in the babel 7 blog post that didn't make the final cut but I thought you'd enjoy:

Here's a very simple example of a custom macro that swaps lineand column imports with the line or column where it appears in the code:

// line-column.macro
module.exports = createMacro(lineColumnMacro)
function lineColumnMacro({ references, babel }) {
	references.line.forEach((referencePath) => {
		const num = referencePath.node.loc.start.line
		referencePath.replaceWith(babel.types.numberLiteral(num))
	})
	references.column.forEach((referencePath) => {
		const num = referencePath.node.loc.start.column
		referencePath.replaceWith(babel.types.numberLiteral(num))
	})
}

And then this code:

`import {line, column} from './line-column.macro'
`
``console.log(`we're at ${line}:${column}`)
console.log(`and now we're at ${line}:${column}`)``

This will be transpiled into:

console.log(`we're at ${3}:${32}`)
console.log(`and now we're at ${4}:${40}`)

And we don't need to change any config to add that custom transform. Writing and using code transforms is a fair amount easier this way. You can't do everything that a full babel plugin can do, but we're only just getting started with this and look forward to what the community will do with this power.

Play around with the above macro example on astexplorer.net. See if you can make it support an import called lineColumn that is replaced with the sum of line and column. Just for fun!

Kent C. Dodds
Written by Kent C. Dodds

Kent C. Dodds is a JavaScript software engineer and teacher. Kent's taught hundreds of thousands of people how to make the world a better place with quality software development tools and practices. He lives with his wife and four kids in Utah.

Learn more about Kent

If you found this article helpful.

You will love these ones as well.