diff --git a/benchmarks/performance/flow.bench.ts b/benchmarks/performance/flow.bench.ts new file mode 100644 index 00000000..00eb7ae5 --- /dev/null +++ b/benchmarks/performance/flow.bench.ts @@ -0,0 +1,21 @@ +import { bench, describe } from 'vitest'; +import { flow as flowToolkit } from 'es-toolkit'; +import { flow as flowLodash } from 'lodash'; + +const flow = flowToolkit; +const flowLo = flowLodash; + +describe('flow', () => { + const add = (x: number, y: number) => x + y; + const square = (n: number) => n * n; + + bench('es-toolkit/flow', () => { + const combined = flow(add, square); + combined(1, 2); + }); + + bench('lodash/flow', () => { + const combined = flowLo(add, square); + combined(1, 2); + }); +}); diff --git a/docs/reference/function/flow.md b/docs/reference/function/flow.md new file mode 100644 index 00000000..d5d15f80 --- /dev/null +++ b/docs/reference/function/flow.md @@ -0,0 +1,48 @@ +# flow + +Creates a function that returns the result of invoking the given functions with the `this` binding of the created function, where each successive invocation is supplied the return value of the previous. + +## Signature + +```typescript +function flow(f: () => R): () => R; +function flow(f1: (...args: A) => R): (...args: A) => R; +function flow(f1: (...args: A) => R1, f2: (a: R1) => R2): (...args: A) => R2; +function flow( + f1: (...args: A) => R1, + f2: (a: R1) => R2, + f3: (a: R2) => R3 +): (...args: A) => R3; +function flow( + f1: (...args: A) => R1, + f2: (a: R1) => R2, + f3: (a: R2) => R3, + f4: (a: R3) => R4 +): (...args: A) => R4; +function flow( + f1: (...args: A) => R1, + f2: (a: R1) => R2, + f3: (a: R2) => R3, + f4: (a: R3) => R4, + f5: (a: R4) => R5 +): (...args: A) => R5; +function flow(...funcs: Array<(...args: any[]) => any>): (...args: any[]) => any; +``` + +### Parameters + +- `funcs` (`Array<(...args: any[]) => any>`): The functions to invoke. + +### Returns + +(`(...args: any[]) => any`): The new composite function. + +## Examples + +```typescript +const add = (x: number, y: number) => x + y; +const square = (n: number) => n * n; + +const combined = flow(add, square); +console.log(combined(1, 2)); // => 9 +``` diff --git a/docs/zh_hans/reference/function/flow.md b/docs/zh_hans/reference/function/flow.md new file mode 100644 index 00000000..0d970de7 --- /dev/null +++ b/docs/zh_hans/reference/function/flow.md @@ -0,0 +1,48 @@ +# flow + +创建一个函数,该函数返回调用给定函数的结果,并将创建函数的 `this` 绑定传递给这些函数,每次调用时将上一次调用的返回值作为参数传递给下一次调用。 + +## 签名 + +```typescript +function flow(f: () => R): () => R; +function flow(f1: (...args: A) => R): (...args: A) => R; +function flow(f1: (...args: A) => R1, f2: (a: R1) => R2): (...args: A) => R2; +function flow( + f1: (...args: A) => R1, + f2: (a: R1) => R2, + f3: (a: R2) => R3 +): (...args: A) => R3; +function flow( + f1: (...args: A) => R1, + f2: (a: R1) => R2, + f3: (a: R2) => R3, + f4: (a: R3) => R4 +): (...args: A) => R4; +function flow( + f1: (...args: A) => R1, + f2: (a: R1) => R2, + f3: (a: R2) => R3, + f4: (a: R3) => R4, + f5: (a: R4) => R5 +): (...args: A) => R5; +function flow(...funcs: Array<(...args: any[]) => any>): (...args: any[]) => any; +``` + +### 参数 + +- `funcs` (`Array<(...args: any[]) => any>`): 需要调用的函数。 + +### 返回值 + +(`(...args: any[]) => any`): 新的组合函数。 + +## 示例 + +```typescript +const add = (x: number, y: number) => x + y; +const square = (n: number) => n * n; + +const combined = flow(add, square); +console.log(combined(1, 2)); // => 9 +``` diff --git a/src/function/flow.spec.ts b/src/function/flow.spec.ts new file mode 100644 index 00000000..4a59b672 --- /dev/null +++ b/src/function/flow.spec.ts @@ -0,0 +1,53 @@ +import { describe, it, expect } from 'vitest'; +import { flow } from './flow'; +import { curry } from '../compat/function/curry'; +import { head } from '../array'; +import { ary } from './ary'; +import { uniq } from '../array'; + +const add = function (x: number, y: number) { + return x + y; +}; + +const square = function (n: number) { + return n * n; +}; + +describe('flow', () => { + it(`\`flow\` should supply each function with the return value of the previous`, () => { + const fixed = function (n: number) { + return n.toFixed(1); + }; + const combined = flow(add, square, fixed); + + expect(combined(1, 2)).toBe('9.0'); + }); + + it(`\`flow\` should return a new function`, () => { + const noop = () => {}; + const combined = flow(noop); + expect(combined).not.toBe(noop); + }); + + it(`\`flow\` should work with a curried function and \`_.head\``, () => { + const curried = curry((i: any) => i); + + const combined = flow(head, curried); + + expect(combined([1])).toBe(1); + }); + + it(`\`flow\` should work with curried functions with placeholders`, () => { + // TODO change to es-toolkit/compat - map + const map = (array: any[], func: (...args: any[]) => any) => { + return array.map(func); + }; + const curried = curry(ary(map, 2), 2); + const getProp = curried(curried.placeholder, (value: { a: any }) => value.a); + const objects = [{ a: 1 }, { a: 2 }, { a: 1 }]; + + const combined = flow(getProp, uniq); + + expect(combined(objects)).toEqual([1, 2]); + }); +}); diff --git a/src/function/flow.ts b/src/function/flow.ts new file mode 100644 index 00000000..a78aaa5e --- /dev/null +++ b/src/function/flow.ts @@ -0,0 +1,155 @@ +/** + * Creates a function that returns the result of invoking the given functions with the `this` binding of the created function, where each successive invocation is supplied the return value of the previous. + * + * @param {() => R} f The function to invoke. + * @returns {() => R} Returns the new composite function. + * + * @example + * function noArgFunc() { + * return 42; + * } + * + * const combined = flow(noArgFunc); + * console.log(combined()); // 42 + */ +export function flow(f: () => R): () => R; +/** + * Creates a function that returns the result of invoking the given functions with the `this` binding of the created function, where each successive invocation is supplied the return value of the previous. + * + * @param {(...args: A) => R} f1 The function to invoke. + * @returns {(...args: A) => R} Returns the new composite function. + * + * @example + * function oneArgFunc(a: number) { + * return a * 2; + * } + * + * const combined = flow(oneArgFunc); + * console.log(combined(5)); // 10 + */ +export function flow(f1: (...args: A) => R): (...args: A) => R; +/** + * Creates a function that returns the result of invoking the given functions with the `this` binding of the created function, where each successive invocation is supplied the return value of the previous. + * + * @param {(...args: A) => R1} f1 The function to invoke. + * @param {(a: R1) => R2} f2 The function to invoke. + * @returns {(...args: A) => R2} Returns the new composite function. + * + * @example + * const add = (x: number, y: number) => x + y; + * const square = (n: number) => n * n; + * + * const combined = flow(add, square); + * console.log(combined(1, 2)); // 9 + */ +export function flow(f1: (...args: A) => R1, f2: (a: R1) => R2): (...args: A) => R2; +/** + * Creates a function that returns the result of invoking the given functions with the `this` binding of the created function, where each successive invocation is supplied the return value of the previous. + * + * @param {(...args: A) => R1} f1 The function to invoke. + * @param {(a: R1) => R2} f2 The function to invoke. + * @param {(a: R2) => R3} f3 The function to invoke. + * @returns {(...args: A) => R3} Returns the new composite function. + * + * @example + * const add = (x: number, y: number) => x + y; + * const square = (n: number) => n * n; + * const double = (n: number) => n * 2; + * + * const combined = flow(add, square, double); + * console.log(combined(1, 2)); // 18 + */ +export function flow( + f1: (...args: A) => R1, + f2: (a: R1) => R2, + f3: (a: R2) => R3 +): (...args: A) => R3; +/** + * Creates a function that returns the result of invoking the given functions with the `this` binding of the created function, where each successive invocation is supplied the return value of the previous. + * + * @param {(...args: A) => R1} f1 The function to invoke. + * @param {(a: R1) => R2} f2 The function to invoke. + * @param {(a: R2) => R3} f3 The function to invoke. + * @param {(a: R3) => R4} f4 The function to invoke. + * @returns {(...args: A) => R4} Returns the new composite function. + * + * @example + * const add = (x: number, y: number) => x + y; + * const square = (n: number) => n * n; + * const double = (n: number) => n * 2; + * const toStr = (n: number) => n.toString(); + * + * const combined = flow(add, square, double, toStr); + * console.log(combined(1, 2)); // '18' + */ +export function flow( + f1: (...args: A) => R1, + f2: (a: R1) => R2, + f3: (a: R2) => R3, + f4: (a: R3) => R4 +): (...args: A) => R4; +/** + * Creates a function that returns the result of invoking the given functions with the `this` binding of the created function, where each successive invocation is supplied the return value of the previous. + * + * @param {(...args: A) => R1} f1 The function to invoke. + * @param {(a: R1) => R2} f2 The function to invoke. + * @param {(a: R2) => R3} f3 The function to invoke. + * @param {(a: R3) => R4} f4 The function to invoke. + * @param {(a: R4) => R5} f5 The function to invoke. + * @returns {(...args: A) => R5} Returns the new composite function. + * + * @example + * const add = (x: number, y: number) => x + y; + * const square = (n: number) => n * n; + * const double = (n: number) => n * 2; + * const toStr = (n: number) => n.toString(); + * const split = (s: string) => s.split(''); + * + * const combined = flow(add, square, double, toStr, split); + * console.log(combined(1, 2)); // ['1', '8'] + */ +export function flow( + f1: (...args: A) => R1, + f2: (a: R1) => R2, + f3: (a: R2) => R3, + f4: (a: R3) => R4, + f5: (a: R4) => R5 +): (...args: A) => R5; +/** + * Creates a function that returns the result of invoking the given functions with the `this` binding of the created function, where each successive invocation is supplied the return value of the previous. + * + * @param {Array<(...args: any[]) => any>} funcs The functions to invoke. + * @returns {(...args: any[]) => any} Returns the new composite function. + * + * @example + * const add = (x: number, y: number) => x + y; + * const square = (n: number) => n * n; + * + * const combined = flow(add, square); + * console.log(combined(1, 2)); // 9 + */ +export function flow(...funcs: Array<(...args: any[]) => any>): (...args: any[]) => any; +/** + * Creates a function that returns the result of invoking the given functions with the `this` binding of the created function, where each successive invocation is supplied the return value of the previous. + * + * @param {Array<(...args: any[]) => any>} funcs The functions to invoke. + * @returns {(...args: any[]) => any} Returns the new composite function. + * + * @example + * const add = (x: number, y: number) => x + y; + * const square = (n: number) => n * n; + * + * const combined = flow(add, square); + * console.log(combined(1, 2)); // 9 + */ +export function flow(...funcs: Array<(...args: any[]) => any>): (...args: any[]) => any { + return function (this: any, ...args: any[]) { + let result = funcs.length ? funcs[0].apply(this, args) : args[0]; + + for (let i = 1; i < funcs.length; i++) { + result = funcs[i].call(this, result); + } + + return result; + }; +} diff --git a/src/function/index.ts b/src/function/index.ts index 511421ac..54d4437b 100644 --- a/src/function/index.ts +++ b/src/function/index.ts @@ -13,3 +13,4 @@ export { partialRight } from './partialRight.ts'; export { rest } from './rest.ts'; export { curry } from './curry.ts'; export { spread } from './spread.ts'; +export { flow } from './flow.ts';