ITADN

Real-world examples of better DX for constructors and factory APIs like `Object.fromEntries`

#314Openjustingrant 创建于 2025-05-29
J
justingrantcommented
> 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 条评论