es-toolkit icon indicating copy to clipboard operation
es-toolkit copied to clipboard

Chainable API

Open mfulton26 opened this issue 1 year ago • 5 comments

I just discovered this library and it looks great. What do you think of supporting a chainable API too?

Pure functions are nice but when a developer needs to use multiple methods in a row it gets annoying to have to define intermediary variables or to nest function calls. JavaScript doesn't have a pipeline operator but prototypes can be safely augmented these days using symbols (rather than strings which can cause conflicts because they are not unique).

Example

import $ from 'es-toolkit/$';
import 'es-toolkit/$/array/chunk';

// Splits an array of numbers into sub-arrays of length 2
[1, 2, 3, 4, 5][$.chunk](2);
// Returns: [[1, 2], [3, 4], [5]]

// Splits an array of strings into sub-arrays of length 3
['a', 'b', 'c', 'd', 'e', 'f', 'g'][$.chunk](3);
// Returns: [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']]

Implementation

Augmenting modules should be able to be programmatically generated from pure functions (e.g. using ts-morph):

// es-toolkit/$.ts
export interface Symbols {};
export default {} as Symbols;
// es-toolkit/$/.symbols/chunk.ts
import $ from 'es-toolkit/$.ts';

const key = 'chunk';
const value = Symbol(`es-toolkit/${key}`);

declare module 'es-toolkit/$.ts' {
  interface Symbols {
    [key]: typeof value;
  }
}

Object.defineProperty($, key, { value });
// es-toolkit/$/array/chunk.ts
import 'es-toolkit/.symbols/chunk';
import { chunk } from 'es-toolkit/array/chunk';

const key = $.chunk;
function value<T>(this: T[], size: number): T[] {
  return chunk(this, size);
}

declare global {
  interface Array<T> {
    [key](size: number): T[];
  }
}

Object.defineProperty(Array.prototype, key, { value });

mfulton26 avatar Aug 22 '24 12:08 mfulton26

Do modifications to the Array.prototype make the sideEffects become true?

D-Sketon avatar Aug 22 '24 12:08 D-Sketon

Do modifications to the Array.prototype make the sideEffects become true?

I'm not familiar with that flag. Is that a webpack specific flag? I think it would, but the side-effects modules could be published under a different package name while reusing code to avoid such.

mfulton26 avatar Aug 22 '24 17:08 mfulton26

After looking at some docs it looks like this can be set to true for only some modules. e.g.

  "sideEffects": ["./src/$/**/*.ts"]

mfulton26 avatar Aug 22 '24 23:08 mfulton26

mutating Array.prototype doesn't sound like the best idea to me

Smrtnyk avatar Oct 08 '24 09:10 Smrtnyk

mutating Array.prototype doesn't sound like the best idea to me

Understandably IMO. Historically this is considered unsafe but with the introduction of Symbol in JavaScript this can now be done safely without name collisions (as is possible with String-based keys but not with unique Symbol-based keys). This is commonly done today with polyfills (e.g. via zloirock/core-js: Standard Library). It is this same polyfill idea/process but applied to user-defined prototype methods using Symbol keys instead of TC39-define prototype methods using String keys.

mfulton26 avatar Oct 08 '24 12:10 mfulton26