From 79da291f72e6ecb3f415dd66fa2de46c0ac6e7b1 Mon Sep 17 00:00:00 2001 From: SUZUKI Sosuke Date: Mon, 13 Nov 2023 12:01:53 +0900 Subject: [PATCH] Revert "Revert "Add blog post for experimental ternaries"" (#15618) --- cspell.json | 4 + website/blog/2023-11-13-curious-ternaries.md | 130 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 website/blog/2023-11-13-curious-ternaries.md diff --git a/cspell.json b/cspell.json index 926bdc114..c6c03436e 100644 --- a/cspell.json +++ b/cspell.json @@ -43,6 +43,7 @@ "Cheung", "chrzosel", "Clemmons", + "clammering", "cliify", "cmds", "codebases", @@ -100,6 +101,7 @@ "Filipe", "Fiorini", "Fisker", + "fizzbuzz", "flamegraph", "foldgutter", "formatprg", @@ -325,6 +327,8 @@ "unparenthesized", "unparseable", "Unrestrict", + "upvoted", + "upvotes", "unstaged", "upvoted", "valourous", diff --git a/website/blog/2023-11-13-curious-ternaries.md b/website/blog/2023-11-13-curious-ternaries.md new file mode 100644 index 000000000..c821ab76f --- /dev/null +++ b/website/blog/2023-11-13-curious-ternaries.md @@ -0,0 +1,130 @@ +--- +author: "Alex Rattray (@rattrayalex)" +authorURL: "https://github.com/rattrayalex" +title: "A curious case of the ternaries" +--- + +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](/blog/2023/11/13/3.1.0)._ + + + +## Introduction + +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. + +## Historical background + +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](https://github.com/prettier/prettier/issues/737). + +So in 2018, we [replaced that with flat ternaries](https://github.com/prettier/prettier/pull/5039), which [seemed](https://github.com/prettier/prettier/pull/4767#issuecomment-401764876) like a good idea at the time, but was [not received well](https://github.com/prettier/prettier/issues/5814) – the issue asking it to be reverted had well over 500 upvotes. + +While we did ultimately [revert back to indented ternaries](https://github.com/prettier/prettier/pull/9559), we wanted to find a better way. + +Over the last few years, we [explored](https://github.com/prettier/prettier/issues/9561) 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. + +## Challenging criteria + +Ideally, we'd find one scheme that would meet our criteria: + +1. In all cases, it should be easy to follow "what's the `if`", "what's the `then`", and "what's the `else`" – and what they map to. +2. The code should fluidly flow from a single ternary, to a chain of 2, to a long chain of simple cases, to something more complex with a few nested conditions. (Most alternatives we explored failed this test). +3. The syntax in JSX, TypeScript conditional expressions (which cannot be expressed with `if`), and normal JS should all look and feel the same. +4. It should scale to nested ternary chains of arbitrary length (imagine a TypeScript conditional type with dozens of alternative cases). + +Indented ternaries clearly [failed (4)](https://github.com/prettier/prettier/pull/9559#issuecomment-720736156), arguably (1), and even (3) – we have almost always printed JSX ternaries in a flat-but-readable format that unfortunately [felt unnatural](https://github.com/prettier/prettier/pull/9552) 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](https://github.com/prettier/prettier/issues/9561#goals:~:text=on%20that%20below.-,Goals,-I%27d%20like%20to). + +## A surprising solution + +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: + +!["I'm not convinced the new version is simpler to read here."](https://user-images.githubusercontent.com/704302/205551054-122f2fc0-fee3-4254-912a-1b97b5cf0c04.png) + +But then, after using it for a bit, they didn't want to go back: + +!["I'm liking the ternaries! I think it makes sense to have them formatted like this. I also got used to them quite quickly as well. \nI agree with this, it takes a very short time to get used to it."](https://user-images.githubusercontent.com/704302/205550887-b780f6ba-b678-4620-a454-255bd5083096.png) + +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 `if` statements. I’m not going back. + +> I used to hate nested ternaries, but I also hate restructuring a nice line of code into `if-else` statements. The new rule adds an understandable, linear `if-else`, `if-else` expression 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 clammering for: [indented ternaries](https://github.com/prettier/prettier/pull/9559). + +## Styling Overview + +So what does this new style look like, anyway? + +Here's a quick, contrived example to show the thinking behind "curious" ternaries: + + +```ts +const animalName = + pet.canBark() ? + pet.isScary() ? + 'wolf' + : 'dog' + : pet.canMeow() ? + 'cat' + : 'probably a bunny'; +``` + +1. Every line that ends with a question mark is an **"if"**. + - If you see `foo ?` it's like asking a question about foo – "if foo? then, …". +2. Every line that starts with a `:` is an **"else"**. + - If you see `: foo` that means, "else, foo". + - If you see `: foo ?` that means "else, if foo?". +3. Every line without `:` or `?` is a **"then"**. + - If you just see `foo`, that means, "then foo". + +And here's the code rewritten to demonstrate "case-style" ternaries: + + +```ts +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: + + +```ts +const animalName = + pet.canSqueak() ? 'mouse' + : pet.canBark() ? + pet.isScary() ? + 'wolf' + : 'dog' + : pet.canMeow() ? 'cat' + : pet.canSqueak() ? 'mouse' + : 'probably a bunny'; +``` + +## Give us your feedback! + +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