Real-world examples of better DX for constructors and factory APIs like `Object.fromEntries`
> more empiric evidence for pipe’s impact would be good. (from https://github.com/tc39/proposal-pipeline-operator/issues/232#issuecomment-2784879225)
Thanks so much for persevering with this proposal. Here's 8 snippets (I could probably provide hundreds more) from our small company's codebase showing how Pipeline could make it easier to use one common API: `Object.fromEntries`.
A few surprising (to me) observations from this exercise:
* Ironically, the biggest usability win was NOT actually moving `Object.entries` to use Pipeline. Instead, a much larger win was removing calls to `reduce` (see the last 2 snippets) that were only in our code because `reduce` is chainable and `Object.entries` is not. Apparently, some developers at our company love chaining so much that they were willing to write much more code (and much more complicated code) just to avoid having to use a nested function call that breaks method chaining. 🤔 It'd be fun to see a plenary slide that includes the line "Pipeline will reduce `reduce`". 😄
* Pipeline and [Prettier](https://prettier.io/) will likely reinforce each other to improve readability. With nested function calls, each call is indented and moved to a new line by Prettier, and all the extra indents in turn cause function calls to exceed Prettier's line length causing their arguments to also be split across lines. Method chains however (including pipes) are moved to new lines at the same nesting level. The result with Pipeline is not only clearer code that reads right-to-left and top-to-bottom, but it's also less indented so each chained call is much less likely to be split across multiple lines, further improving readability.
* We don't really use FP. In our codebase, Pipeline would simply make our existing, non-FP code easier to read and easier to write. You don't need to love FP to love Pipeline.
Note that `Object.entries` is not an outlier. I could provide a similar laundry list of snippets for all popular constructors and factory methods like `[...new Set(array)]` (for deduping arrays), `Temporal.*.from`, `Array.from`, etc. And that's just builtins; any class constructor or factory method in our own code or in libraries we use would get easier to use chainably.
```ts
// current
return Object.fromEntries(
Object.entries(value).map(([k, v]) => [typeof v === 'string' ? `${k}.untranslated` : k, transformValue(v)])
);
// with Pipeline
return Object.entries(value)
.map(([k, v]) => [typeof v === 'string' ? `${k}.untranslated` : k, transformValue(v)])
|> Object.fromEntries(%);
// current
const result = Object.fromEntries(
Object.entries(grouped).map(([machineId, attempts]) => [
machineId,
attempts[0],
])
);
// with Pipeline
const result = Object.entries(grouped)
.map(([machineId, attempts]) => [machineId, attempts[0]])
|> Object.fromEntries(%);
// current
const trimmed = Object.fromEntries(
Object.entries(payload.message).map(([key, value]) => [
key,
value ? value.trim() : unknownLabel,
]),
);
// with Pipeline
const trimmed = Object.entries(payload.message)
.map(([key, value]) => [key, value ? value.trim() : unknownLabel])
|> Object.fromEntries(%);
// current
const MdIconsTransformedKeys = Object.fromEntries(
Object.entries(MdIcons).map(([iconName, icon]) => [
transformIconName(iconName),
icon,
]);
// with Pipeline
const MdIconsTransformedKeys = Object.entries(MdIcons)
.map(([iconName, icon]) => [transformIconName(iconName), icon])
|> Object.fromEntries(%);
// current
return Object.fromEntries(
this.$storeTS.state.printer.printer.material_manager.materials_list.map(m => [m.name, m.color])
);
// with Pipeline
return this.$storeTS.state.printer.printer.material_manager.materials_list
.map(m => [m.name, m.color])
|> Object.fromEntries(%);
// current
return Object.entries(localStorage)
.map(([k, v]) => [String(k), v])
.filter(([k, _v]) => k.startsWith(FLAG_PREFIX))
.reduce(
(acc, [k, v]) => ({ ...acc, [k.slice(FLAG_PREFIX.length)]: !!v }),
{}
);
// with Pipeline
return Object.entries(localStorage)
.map(([k, v]) => [String(k), v])
.filter(([k, _v]) => k.startsWith(FLAG_PREFIX))
.map([k, v]) => [k.slice(FLAG_PREFIX.length), !!v])
|> Object.fromEntries(%);
// current
return Object.entries(colors)
.toSorted(([_nameA, colorA], [_nameB, colorB]) => {
const lightnessA = parseToHsl(colorA).lightness;
const lightnessB = parseToHsl(colorB).lightness;
return lightnessA - lightnessB;
})
.reduce((acc, [name, color]) => {
acc[name] = color;
return acc;
}, {} as Record<string, string>);
// with Pipeline
return Object.entries(colors)
.toSorted(([_nameA, colorA], [_nameB, colorB]) => {
const lightnessA = parseToHsl(colorA).lightness;
const lightnessB = parseToHsl(colorB).lightness;
return lightnessA - lightnessB;
})
|> Object.fromEntries(%) as Record<string, string>);
// current
return Object.keys(state.availableCommands)
.filter((key) => key.endsWith('CALIBRATE'))
.reduce((o, key) => {
return {
...o,
[key]: state.availableCommands[key],
};
}, {} as GcodeCommands);
// with Pipeline
return Object.keys(state.availableCommands)
.filter((key) => key.endsWith('CALIBRATE'))
.map((key) => [key, state.availableCommands[key]]
|> Object.fromEntries(%) as GcodeCommands);
```
2 条评论