website/blog/2023-11-13-curious-ternaries.md
Ternary formatting has always been a challenge, and we're finally addressing it in v3.1.0 with the introduction of a novel formatting style.
Read on for our journey and the motivation behind this change, along with early developer feedback and an overview of the "curious ternaries" style.
Please give the --experimental-ternaries option a try and let us know what you think!
For a quick tl;dr, see the release post.
<!-- truncate -->Formatting nested ternaries nicely in a wide variety of scenarios is a surprisingly tricky challenge.
Developers have long found them so confusing to read that they end up just refactoring their code to an ugly series of if-else statements, often with a let declaration, an iife, or a separate function entirely.
According to beta testers, the new formatting style we've developed can take some getting used to, but ultimately allows ternaries to be practically used as a concise form of if-else-expressions in modern codebases.
Prettier's original, naïve approach – just add indentation to each level of a nested ternary – worked fine in simple cases, but obviously doesn't scale to long chains of nested ternaries and had other problems.
So in 2018, we replaced that with flat ternaries, which seemed like a good idea at the time, but was not received well – the issue asking it to be reverted had well over 500 upvotes.
While we did ultimately revert back to indented ternaries, we wanted to find a better way.
Over the last few years, we explored and experimented with many, many possible solutions which would be as readable as indented ternaries in common cases, but also scale to work well in a wider variety of situations.
Ideally, we'd find one scheme that would meet our criteria:
if", "what's the then", and "what's the else" – and what they map to.if), and normal JS should all look and feel the same.Indented ternaries clearly failed (4), arguably (1), and even (3) – we have almost always printed JSX ternaries in a flat-but-readable format that unfortunately felt unnatural outside of JSX.
Many people in the community were excited about a "case-style", drawing inspiration from the match syntax from languages like Rust or OCaml, but it did not meet (2) and other goals.
The good news is that we found a formatting algorithm that met our criteria. The bad news is that it's novel, and thus unfamiliar to most developers.
In beta testing this feature, we found developers were quite skeptical when they first saw it:
But then, after using it for a bit, they didn't want to go back:
Another developer had this to say:
My first hour with the rule on, it felt a little odd. But by hour two, I’d used it a few times to solve problems that otherwise would have been ugly refactors to
ifstatements. I’m not going back.
I used to hate nested ternaries, but I also hate restructuring a nice line of code into
if-elsestatements. The new rule adds an understandable, linearif-else,if-elseexpression to the language and is much nicer than multiple ternaries as nested branches.
So we felt we had a winning formula, but we knew it could be a jarring introduction to the community.
As a result, we decided to put this new formatting behind a temporary --experimental-ternaries option for a few months, and in the meantime go ahead and ship what the community has been clamoring for: indented ternaries.
So what does this new style look like, anyway?
Here's a quick, contrived example to show the thinking behind "curious" ternaries:
<!-- prettier-ignore -->const animalName =
pet.canBark() ?
pet.isScary() ?
'wolf'
: 'dog'
: pet.canMeow() ? 'cat'
: 'probably a bunny';
foo ? it's like asking a question about foo – "if foo? then, …".: is an "else".
: foo that means, "else, foo".: foo ? that means "else, if foo?".: or ? is a "then".
foo, that means, "then foo".And here's the code rewritten to demonstrate "case-style" ternaries:
<!-- prettier-ignore -->const animalName =
pet.isScary() ? 'wolf'
: pet.canBark() ? 'dog'
: pet.canMeow() ? 'cat'
: 'probably a bunny';
You can see this is a nice concise way to get something approaching match-style syntax in JavaScript, with just the humble ternary operator (albeit missing several features).
Our new formatting is a fluid blend of "curious" ternaries (where the question mark is always at the end of the line), and "case-style" ternaries, where the question mark is in the middle of the line.
For example:
<!-- prettier-ignore -->const animalName =
pet.canSqueak() ? 'mouse'
: pet.canBark() ?
pet.isScary() ?
'wolf'
: 'dog'
: pet.canMeow() ? 'cat'
: pet.canSqueak() ? 'mouse'
: 'probably a bunny';
We hope you like the more readable new default, and we really hope you give the new --experimental-ternaries option a try for a few weeks and let us know what you think.
Please give us feedback via Google Forms: https://forms.gle/vwEuboCobTVhEkt66