TL;DR It is an operator that parallels with
map.emplace(key, { update })
, but for ordinary references (particularly, for objects with lookup purpose).
What?
We currently have:
value ||= defaultValue;
and
value &&= replacementValue;
The above are totally fine if value
won’t ever be 0
’s and ""
’s (or if they really indicates an “absence”, but I believe it is seldom the case).
But what if not?
value ??= defaultValue;
value ⬚= replacementValue;
What fits the ⬚
in the second line?
Currently, we would have to write if (value != null) value = replacementValue;
or value == null ? undefined : replacementValue
, which is verbose and unergonomic. This is true even with the Nullish unary operator `?` (if (?value) value = replacementValue;
).
Solution: The non-nullish coalescing assignment operator
In real world code, Object.create(null)
(or even just {}
) is used instead of new Map()
for various reasons. The obvious one is the lack of equivalent of setdefault
in Python, leading to the proposal of the emplace method on Map
s.
const map = Object.create(null);
// insert
map[key] ??= [0];
// update
map[key] !!= map[key].sort((a, b) => a - b);
On the other hand, writing
value &&= normalizeOption(value);
is likely to be a bug as it doesn’t normalize 0
’s and ""
’s. The new operator prevents potential bugs due to falsy values.
The actual operator is to be bikeshedded, since it conflicts with TypeScript’s non-null(ish) assertion operator (it is mostly useless to write such an idempotent operator twice though) or in case the Non-null assertion operator (aka Swift's force unwrap) is pushed forward and is allowed on the left-hand side.
Another point for discussion is that whether the non-nullish coalescing operator !!
should be in the proposal. I am not sure if writing a == null ? undefined : b
as a !! b
really worths.
Example
The following example is in TypeScript so as to better convey the idea behind:
In an external API
export function createModal(): Modal {
// ...
}
export function displayMessage(
modal: Modal,
message: string,
color: "black" | "gray" | "white" | number | undefined = prefersColorScheme() === "dark" ? "white" : "black"
): void {
// ...
}
In the application
import { createModal, displayMessage } from "some-external-api";
type RGBColor = number;
type NonLegacyLiteralColors = "black" | "gray" | "white";
/** @deprecated */
type LegacyLiteralColors = "BLACK" | "GRAY" | "WHITE" | "GREY" | "grey";
type AllColors = LegacyLiteralColors | NonLegacyLiteralColors | RGBColor;
function normalizeColor(color: AllColors): NonLegacyLiteralColors | RGBColor {
if (typeof color === "number") return color;
color = color.toLowerCase() as "black" | "gray" | "grey" | "white";
return color === "grey" ? "gray" : color;
}
export function showModal(
message: string,
options: { color?: AllColors | undefined } = {}
) {
const modal = createModal();
let { color } = options;
// `&&=` is not suitable since `color` may be 0x000000
color !!= normalizeColor(color);
displayMessage(modal, message, color);
// or
const { color } = options;
displayMessage(modal, message, color !! normalizeColor(color));
}