feat(curry): add curry function (#187)

* feat. Add utility types

* feat. Add curry function

* feat. add error throw

* feat. add spec of curry

* fix. fix type error when optional parameter exist

* fix. fix type when rest parameters exist

* fix. fix types error

* fix. fix all type errors

* refactor. rename OptionalStartIdx

* test. add test codes when rest parameters exist

* feat. add  method to flexibleCurry

* feat. fix all type errors and functional errors

* chore. add benchmark

* chore. fix comments

* refactor. enhance type of flexibleCurry

* refactor. refactor arrow function -> normal function

* feat. correctly infer the errored type when the run method is called while the argument to be received is not optional

* docs. add docs about curry

* refactor. file refactor naming case of types

* fix. fix types error caused by renaming of case

* fix. fix errors same as last commit

* fix. fix errors same as last commit

* feat. keep curry function and type simple

* fix. fix test codes with updated implementation

* Update src/function/index.ts

* feat(curry): Fix curry types

* feat(curry): Fix curry types

* feat(curry): Fix curry types

* Remove ramda

---------

Co-authored-by: Sojin Park <raon0211@toss.im>
Co-authored-by: Sojin Park <raon0211@gmail.com>
This commit is contained in:
seungrodotlee 2024-09-14 14:24:56 +09:00 committed by GitHub
parent 0c9de4747a
commit 9364390715
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 399 additions and 1 deletions

15
benchmarks/curry.bench.ts Normal file
View File

@ -0,0 +1,15 @@
import { bench, describe } from 'vitest';
import { curry as curryToolkit } from 'es-toolkit';
import { curry as curryLodash } from 'lodash';
describe('curry', () => {
const fn = (a: number, b: string, c: boolean) => ({ a, b, c });
bench('es-toolkit/curry', () => {
curryToolkit(fn)(1)('a')(true);
});
bench('lodash/curry', () => {
curryLodash(fn)(1)('a')(true);
});
});

View File

@ -0,0 +1,42 @@
# curry
関数をカリー化し、1回に1つのパラメータで呼び出すことができ、次のパラメータを取る新しい関数を返し続けます。
このプロセスはすべてのパラメータが提供されるまで続き、すべてのパラメータが蓄積された段階で元の関数が呼び出されます。
## インターフェース
```typescript
function curry<R>(func: () => R): () => R;
function curry<P, R>(func: (p: P) => R): (p: P) => R;
function curry<P1, P2, R>(func: (p1: P1, p2: P2) => R): (p1: P1) => (p2: P2) => R;
function curry<P1, P2, P3, R>(func: (p1: P1, p2: P2, p3: P3) => R): (p1: P1) => (p2: P2) => (p3: P3) => R;
function curry<P1, P2, P3, P4, R>(func: (p1: P1, p2: P2, p3: P3, p4: P4) => R): (p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => R;
function curry<P1, P2, P3, P4, P5, R>(func: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => R): (p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => (p5: P5) => R;
function curry(func: (...args: any[]) => any): (...args: any[]) => any;
function curry(func: (...args: any[]) => any): (...args: any[]) => any;
```
### パラメータ
- `func` (`(...args: any[]) => any`): カリー化する関数。
### 戻り値
(`(...args: any[]) => any`): 1回に1つのパラメータで呼び出すことができるカリー化された関数。
## 例
```typescript
function sum(a: number, b: number, c: number) {
return a + b + c;
}
const curriedSum = curry(sum);
// パラメータ `a` に値 `10` を与えます。
const sum10 = curriedSum(10);
// パラメータ `b` に値 `15` を与えます。
const sum25 = sum10(15);
// パラメータ `c` に値 `5` を与えます。
// 関数 'sum' はすべてのパラメータを受け取ったので、今値を返します。
const result = sum25(5);

View File

@ -0,0 +1,44 @@
# curry
함수를 커링하여 한 번에 하나의 파라미터로 호출할 수 있도록 하고, 다음 파라미터를 받는 새로운 함수를 반환해요.
모든 파라미터가 제공되면, 이때 원래 함수가 지금까지 주어진 파라미터로 호출돼요.
## 인터페이스
```typescript
function curry<R>(func: () => R): () => R;
function curry<P, R>(func: (p: P) => R): (p: P) => R;
function curry<P1, P2, R>(func: (p1: P1, p2: P2) => R): (p1: P1) => (p2: P2) => R;
function curry<P1, P2, P3, R>(func: (p1: P1, p2: P2, p3: P3) => R): (p1: P1) => (p2: P2) => (p3: P3) => R;
function curry<P1, P2, P3, P4, R>(func: (p1: P1, p2: P2, p3: P3, p4: P4) => R): (p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => R;
function curry<P1, P2, P3, P4, P5, R>(func: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => R): (p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => (p5: P5) => R;
function curry(func: (...args: any[]) => any): (...args: any[]) => any;
```
### 파라미터
- `func` (`(...args: any[]) => any`): 커링할 함수예요.
### 반환 값
(`(...args: any[]) => any`): 한 번에 하나의 파라미터로 호출할 수 있는 커링된 함수예요.
## 예시
```typescript
function sum(a: number, b: number, c: number) {
return a + b + c;
}
const curriedSum = curry(sum);
// 파라미터 `a`로 값 `10`을 제공해요.
const sum10 = curriedSum(10);
// 파라미터 `b`로 값 `15`을 제공해요.
const sum25 = sum10(15);
// 파라미터 `c`로 값 `5`을 제공해요.
// 함수 'sum'은 모든 파라미터를 받았기 때문에, 이제 값을 반환해요.
const result = sum25(5);
```

View File

@ -0,0 +1,43 @@
# curry
Curries a function, allowing it to be called with a single argument at a time and returning a new function that takes the next argument.
This process continues until all arguments have been provided, at which point the original function is called with all accumulated arguments.
## Signature
```typescript
function curry<R>(func: () => R): () => R;
function curry<P, R>(func: (p: P) => R): (p: P) => R;
function curry<P1, P2, R>(func: (p1: P1, p2: P2) => R): (p1: P1) => (p2: P2) => R;
function curry<P1, P2, P3, R>(func: (p1: P1, p2: P2, p3: P3) => R): (p1: P1) => (p2: P2) => (p3: P3) => R;
function curry<P1, P2, P3, P4, R>(func: (p1: P1, p2: P2, p3: P3, p4: P4) => R): (p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => R;
function curry<P1, P2, P3, P4, P5, R>(func: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => R): (p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => (p5: P5) => R;
function curry(func: (...args: any[]) => any): (...args: any[]) => any;
```
### Parameters
- `func` (`Function`): The function to curry.
### Returns
(`Function`): A curried function that can be called with a single argument at a time.
## Examples
```typescript
function sum(a: number, b: number, c: number) {
return a + b + c;
}
const curriedSum = curry(sum);
// The parameter `a` should be given the value `10`.
const sum10 = curriedSum(10);
// The parameter `b` should be given the value `15`.
const sum25 = sum10(15);
// The parameter `c` should be given the value `5`. The function 'sum' has received all its arguments and will now return a value.
const result = sum25(5);
```

View File

@ -0,0 +1,42 @@
# curry
将一个函数柯里化,允许它每次只用一个参数调用,并返回一个接受下一个参数的新函数。这个过程会继续,直到所有参数都已提供,此时将使用所有累积的参数调用原始函数。
## 签名
```typescript
function curry<R>(func: () => R): () => R;
function curry<P, R>(func: (p: P) => R): (p: P) => R;
function curry<P1, P2, R>(func: (p1: P1, p2: P2) => R): (p1: P1) => (p2: P2) => R;
function curry<P1, P2, P3, R>(func: (p1: P1, p2: P2, p3: P3) => R): (p1: P1) => (p2: P2) => (p3: P3) => R;
function curry<P1, P2, P3, P4, R>(func: (p1: P1, p2: P2, p3: P3, p4: P4) => R): (p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => R;
function curry<P1, P2, P3, P4, P5, R>(func: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => R): (p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => (p5: P5) => R;
function curry(func: (...args: any[]) => any): (...args: any[]) => any;
```
### 参数
- `func` (`(...args: any[]) => any`): 要进行柯里化的函数。
### 返回值
(`(...args: any[]) => any`): 一个可以每次调用一个参数的柯里化函数。
## 示例
```typescript
function sum(a: number, b: number, c: number) {
return a + b + c;
}
const curriedSum = curry(sum);
// The parameter `a` should be given the value `10`.
const sum10 = curriedSum(10);
// The parameter `b` should be given the value `15`.
const sum25 = sum10(15);
// The parameter `c` should be given the value `5`. The function 'sum' has received all its arguments and will now return a value.
const result = sum25(5);
```

View File

@ -171,4 +171,4 @@
"format": "prettier --write .",
"transform": "jscodeshift -t ./.scripts/tests/transform-lodash-test.ts $0 && prettier --write $0"
}
}
}

View File

@ -0,0 +1,31 @@
import { describe, expect, expectTypeOf, it } from 'vitest';
import { curry } from './curry';
describe('curry', () => {
it('should return original type of function when function without any arguments received', () => {
const fn = () => 'test';
const curried = curry(fn);
expect(curried()).toBe('test');
});
it('should curry based on the number of arguments given', () => {
const fn = (a: number, b: number, c: number, d: number) => [a, b, c, d];
const curried = curry(fn);
const expected = [1, 2, 3, 4];
expect(curried(1)(2)(3)(4)).toEqual(expected);
});
it('should inference type correctly', () => {
const fn = (a: number, b: string, c: boolean) => ({ a, b, c });
const curried = curry(fn);
expectTypeOf(curried).parameters.toEqualTypeOf<[number]>();
expectTypeOf(curried(1)).parameters.toEqualTypeOf<[string]>();
expectTypeOf(curried(1)).not.toEqualTypeOf<{ a: number; b: string; c: boolean }>();
expectTypeOf(curried(1)('a')).parameters.toEqualTypeOf<[boolean]>();
expectTypeOf(curried(1)('a')).not.toEqualTypeOf<{ a: number; b: string; c: boolean }>();
expectTypeOf(curried(1)('a')(true)).toEqualTypeOf<{ a: number; b: string; c: boolean }>();
});
});

180
src/function/curry.ts Normal file
View File

@ -0,0 +1,180 @@
/**
* Curries a function, allowing it to be called with a single argument at a time and returning a new function that takes the next argument.
* This process continues until all arguments have been provided, at which point the original function is called with all accumulated arguments.
*
* @param {() => R} func - The function to curry.
* @returns {() => R} A curried function.
*
* @example
* function noArgFunc() {
* return 42;
* }
* const curriedNoArgFunc = curry(noArgFunc);
* console.log(curriedNoArgFunc()); // 42
*/
export function curry<R>(func: () => R): () => R;
/**
* Curries a function, allowing it to be called with a single argument at a time and returning a new function that takes the next argument.
* This process continues until all arguments have been provided, at which point the original function is called with all accumulated arguments.
*
* @param {(p: P) => R} func - The function to curry.
* @returns {(p: P) => R} A curried function.
*
* @example
* function oneArgFunc(a: number) {
* return a * 2;
* }
* const curriedOneArgFunc = curry(oneArgFunc);
* console.log(curriedOneArgFunc(5)); // 10
*/
export function curry<P, R>(func: (p: P) => R): (p: P) => R;
/**
* Curries a function, allowing it to be called with a single argument at a time and returning a new function that takes the next argument.
* This process continues until all arguments have been provided, at which point the original function is called with all accumulated arguments.
*
* @param {(p1: P1, p2: P2) => R} func - The function to curry.
* @returns {(p1: P1) => (p2: P2) => R} A curried function.
*
* @example
* function twoArgFunc(a: number, b: number) {
* return a + b;
* }
* const curriedTwoArgFunc = curry(twoArgFunc);
* const add5 = curriedTwoArgFunc(5);
* console.log(add5(10)); // 15
*/
export function curry<P1, P2, R>(func: (p1: P1, p2: P2) => R): (p1: P1) => (p2: P2) => R;
/**
* Curries a function, allowing it to be called with a single argument at a time and returning a new function that takes the next argument.
* This process continues until all arguments have been provided, at which point the original function is called with all accumulated arguments.
*
* @param {(p1: P1, p2: P2, p3: P3, p4: P4) => R} func - The function to curry.
* @returns {(p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => R} A curried function.
*
* @example
* function fourArgFunc(a: number, b: number, c: number, d: number) {
* return a + b + c + d;
* }
* const curriedFourArgFunc = curry(fourArgFunc);
* const add1 = curriedFourArgFunc(1);
* const add2 = add1(2);
* const add3 = add2(3);
* console.log(add3(4)); // 10
*/
export function curry<P1, P2, P3, R>(func: (p1: P1, p2: P2, p3: P3) => R): (p1: P1) => (p2: P2) => (p3: P3) => R;
/**
* Curries a function, allowing it to be called with a single argument at a time and returning a new function that takes the next argument.
* This process continues until all arguments have been provided, at which point the original function is called with all accumulated arguments.
*
* @param {(p1: P1, p2: P2, p3: P3, p4: P4) => R} func - The function to curry.
* @returns {(p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => R} A curried function.
*
* @example
* function fourArgFunc(a: number, b: number, c: number, d: number) {
* return a + b + c + d;
* }
* const curriedFourArgFunc = curry(fourArgFunc);
* const add1 = curriedFourArgFunc(1);
* const add2 = add1(2);
* const add3 = add2(3);
* console.log(add3(4)); // 10
*/
export function curry<P1, P2, P3, P4, R>(
func: (p1: P1, p2: P2, p3: P3, p4: P4) => R
): (p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => R;
/**
* Curries a function, allowing it to be called with a single argument at a time and returning a new function that takes the next argument.
* This process continues until all arguments have been provided, at which point the original function is called with all accumulated arguments.
*
* @param {(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => R} func - The function to curry.
* @returns {(p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => (p5: P5) => R} A curried function.
*
* @example
* function fiveArgFunc(a: number, b: number, c: number, d: number, e: number) {
* return a + b + c + d + e;
* }
* const curriedFiveArgFunc = curry(fiveArgFunc);
* const add1 = curriedFiveArgFunc(1);
* const add2 = add1(2);
* const add3 = add2(3);
* const add4 = add3(4);
* console.log(add4(5)); // 15
*/
export function curry<P1, P2, P3, P4, P5, R>(
func: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => R
): (p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => (p5: P5) => R;
/**
* Curries a function, allowing it to be called with a single argument at a time and returning a new function that takes the next argument.
* This process continues until all arguments have been provided, at which point the original function is called with all accumulated arguments.
*
* @param {(...args: any[]) => any} func - The function to curry.
* @returns {(...args: any[]) => any} A curried function that can be called with a single argument at a time.
*
* @example
* function sum(a: number, b: number, c: number) {
* return a + b + c;
* }
*
* const curriedSum = curry(sum);
*
* // The parameter `a` should be given the value `10`.
* const sum10 = curriedSum(10);
*
* // The parameter `b` should be given the value `15`.
* const sum25 = sum10(15);
*
* // The parameter `c` should be given the value `5`. The function 'sum' has received all its arguments and will now return a value.
* const result = sum25(5);
*/
export function curry(func: (...args: any[]) => any): (...args: any[]) => any;
/**
* Curries a function, allowing it to be called with a single argument at a time and returning a new function that takes the next argument.
* This process continues until all arguments have been provided, at which point the original function is called with all accumulated arguments.
*
* @param {(...args: any[]) => any} func - The function to curry.
* @returns {(...args: any[]) => any} A curried function that can be called with a single argument at a time.
*
* @example
* function sum(a: number, b: number, c: number) {
* return a + b + c;
* }
*
* const curriedSum = curry(sum);
*
* // The parameter `a` should be given the value `10`.
* const sum10 = curriedSum(10);
*
* // The parameter `b` should be given the value `15`.
* const sum25 = sum10(15);
*
* // The parameter `c` should be given the value `5`. The function 'sum' has received all its arguments and will now return a value.
* const result = sum25(5);
*/
export function curry(func: (...args: any[]) => any): (...args: any[]) => any {
if (func.length === 0 || func.length === 1) {
return func;
}
return function (arg: any) {
return makeCurry(func, func.length, [arg]);
} as any;
}
function makeCurry<F extends (...args: any) => any>(origin: F, argsLength: number, args: any[]) {
if (args.length === argsLength) {
return origin(...args);
} else {
const next = function (arg: Parameters<F>[0]) {
return makeCurry(origin, argsLength, [...args, arg]);
};
return next as any;
}
}

View File

@ -11,4 +11,5 @@ export { unary } from './unary.ts';
export { partial } from './partial.ts';
export { partialRight } from './partialRight.ts';
export { rest } from './rest.ts';
export { curry } from './curry.ts';
export { spread } from './spread.ts';