Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compat): implement curryRight #645

Merged
merged 3 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions benchmarks/performance/curryRight.bench.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { bench, describe } from 'vitest';
import { curryRight as curryRightToolkit } from 'es-toolkit';
import { curryRight as curryRightLodash } from 'lodash';
import { curryRight as curryRightToolkit_ } from 'es-toolkit';
import { curryRight as curryRightToolkitCompat_ } from 'es-toolkit/compat';
import { curryRight as curryRightLodash_ } from 'lodash';

const curryRight = curryRightToolkit;
const curryRightLo = curryRightLodash;
const curryRightToolkit = curryRightToolkit_;
const curryRightToolkitCompat = curryRightToolkitCompat_;
const curryRightLodash = curryRightLodash_;

describe('curryRight', () => {
const fn = (a: number, b: string, c: boolean) => ({ a, b, c });

bench('es-toolkit/curry', () => {
curryRight(fn)(1)('a')(true);
bench('es-toolkit/curryRight', () => {
curryRightToolkit(fn)(true)('a')(1);
});

bench('lodash/curry', () => {
curryRightLo(fn)(1)('a')(true);
bench('es-toolkit/compat/curryRight', () => {
curryRightToolkitCompat(fn)(true)('a')(1);
});

bench('lodash/curryRight', () => {
curryRightLodash(fn)(true)('a')(1);
});
});
55 changes: 55 additions & 0 deletions docs/reference/function/curryRight.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,58 @@ const sum25 = sum10(15);
// The parameter `a` should be given the value `5`. The function 'sum' has received all its arguments and will now return a value.
const result = sum25(5); // 30
```

## Lodash Compatibility

Import `curryRight` from `es-toolkit/compat` for full compatibility with lodash.

### Signature

```typescript
function curryRight(
func: (...args: any[]) => any,
arity: number = func.length,
guard?: unknown
): ((...args: any[]) => any) & { placeholder: typeof curryRight.placeholder };

namespace curryRight {
placeholder: symbol;
}
```

- `curryRight` accepts an additional numeric parameter, `arity`, which specifies the number of arguments the function should accept.
- Defaults to the `length` property of the function. If `arity` is negative or `NaN`, it will be converted to `0`. If it's a fractional number, it will be rounded down to the nearest integer.
- `guard` enables use as an iteratee for methods like `Array#map`.
- The `curryRight.placeholder` value, which defaults to a `symbol`, may be used as a placeholder for partially applied arguments.
- Unlike the native `curryRight`, this function allows multiple arguments to be called at once and returns a new function that accepts the remaining arguments.

### Examples

```typescript
import { curryRight } from 'es-toolkit/compat';

const abc = function (a, b, c) {
return [a, b, c];
};

const curried = curryRight(abc);

curried(3)(2)(1);
// => [1, 2, 3]

curried(2, 3)(1);
// => [1, 2, 3]

curried(1, 2, 3);
// => [1, 2, 3]

// Curried with placeholders.
curried(3)(curryRight.placeholder, 2)(1);
// => [1, 2, 3]

// Curried with arity.
curried = curryRight(abc, 2);

curried(2)(1);
// => [1, 2]
```
55 changes: 55 additions & 0 deletions docs/zh_hans/reference/function/curryRight.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,58 @@ const sum25 = sum10(15);
// 参数 `a` 应该被赋值为 `5`。函数 'sum' 已经接收到了所有参数,现在将返回一个值。
const result = sum25(5); // 30
```

## Lodash 兼容性

从 `es-toolkit/compat` 中导入 `curryRight` 以实现与 lodash 的完全兼容。

### 签名

```typescript
function curryRight(
func: (...args: any[]) => any,
arity: number = func.length,
guard?: unknown
): ((...args: any[]) => any) & { placeholder: typeof curryRight.placeholder };

namespace curryRight {
placeholder: symbol;
}
```

- `curryRight` 接受一个额外的数值参数 `arity`,该参数指定了函数的参数数量。
- 默认为 `func.length`,如果 `arity` 为负数或 `NaN`,则会被转换为 `0`。如果它是一个小数,则会被向下取整到最接近的整数。
- `guard` 使其可以用作 `Array#map` 等方法的迭代器。
- `curryRight.placeholder` 值默认为一个 `symbol`,可以用作部分应用参数的占位符。
- 不像原生的 `curryRight`,这个方法允许每次使用多个参数调用,并返回一个接受剩余参数的新函数。

### 示例

```typescript
import { curryRight } from 'es-toolkit/compat';

const abc = function (a, b, c) {
return [a, b, c];
};

const curried = curryRight(abc);

curried(3)(2)(1);
// => [1, 2, 3]

curried(2, 3)(1);
// => [1, 2, 3]

curried(1, 2, 3);
// => [1, 2, 3]

// Curried with placeholders.
curried(3)(curryRight.placeholder, 2)(1);
// => [1, 2, 3]

// Curried with arity.
curried = curryRight(abc, 2);

curried(2)(1);
// => [1, 2]
```
159 changes: 159 additions & 0 deletions src/compat/function/curryRight.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { describe, expect, it } from 'vitest';
import { bind } from './bind';
import { curryRight } from './curryRight';
import { partial } from '../../function/partial';
import { partialRight } from '../../function/partialRight';

describe('curryRight', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function fn(_a: unknown, _b: unknown, _c: unknown, _d: unknown) {
// eslint-disable-next-line prefer-rest-params
return Array.from(arguments);
}

it('should curry based on the number of arguments given', () => {
const curried = curryRight(fn),
expected = [1, 2, 3, 4];

expect(curried(4)(3)(2)(1)).toEqual(expected);
expect(curried(3, 4)(1, 2)).toEqual(expected);
expect(curried(1, 2, 3, 4)).toEqual(expected);
});

it('should allow specifying `arity`', () => {
const curried = curryRight(fn, 3),
expected = [1, 2, 3];

expect(curried(3)(1, 2)).toEqual(expected);
expect(curried(2, 3)(1)).toEqual(expected);
expect(curried(1, 2, 3)).toEqual(expected);
});

it('should coerce `arity` to an integer', () => {
const values = ['0', 0.6, 'xyz'],
expected = values.map(() => []);

// @ts-expect-error - unusual arity type
const actual = values.map(arity => curryRight(fn, arity)());

expect(actual).toEqual(expected);
// @ts-expect-error - unusual arity type
expect(curryRight(fn, '2')(1)(2)).toEqual([2, 1]);
});

it('should support placeholders', () => {
const curried = curryRight(fn),
ph = curried.placeholder;

expect(curried(4)(2, ph)(1, ph)(3)).toEqual([1, 2, 3, 4]);
expect(curried(3, ph)(4)(1, ph)(2)).toEqual([1, 2, 3, 4]);
expect(curried(ph, ph, 4)(ph, 3)(ph, 2)(1)).toEqual([1, 2, 3, 4]);
expect(curried(ph, ph, ph, 4)(ph, ph, 3)(ph, 2)(1)).toEqual([1, 2, 3, 4]);
});

it('should persist placeholders', () => {
const curried = curryRight(fn),
ph = curried.placeholder,
actual = curried('a', ph, ph, ph)('b')(ph)('c')('d');

expect(actual).toEqual(['a', 'b', 'c', 'd']);
});

it('should provide additional arguments after reaching the target arity', () => {
const curried = curryRight(fn, 3);
expect(curried(4)(1, 2, 3)).toEqual([1, 2, 3, 4]);
expect(curried(4, 5)(1, 2, 3)).toEqual([1, 2, 3, 4, 5]);
expect(curried(1, 2, 3, 4, 5, 6)).toEqual([1, 2, 3, 4, 5, 6]);
});

it('should create a function with a `length` of `0`', () => {
const curried = curryRight(fn);
expect(curried.length).toBe(0);
expect(curried(4).length).toBe(0);
expect(curried(3, 4).length).toBe(0);

const curried2 = curryRight(fn, 4);
expect(curried2.length).toBe(0);
expect(curried2(4).length).toBe(0);
expect(curried2(3, 4).length).toBe(0);
});

it('should ensure `new curried` is an instance of `func`', () => {
function Foo(value: unknown) {
return value && object;
}

const curried = curryRight(Foo);
const object = {};

// @ts-expect-error - curried is a constructor
expect(new curried(false) instanceof Foo);

// @ts-expect-error - curried is a constructor
expect(new curried(true)).toBe(object);
});

it('should use `this` binding of function', () => {
const fn = function (this: any, a: string | number, b: string | number, c: string | number) {
const value = this || {};
return [value[a], value[b], value[c]];
};

const object: any = { a: 1, b: 2, c: 3 },
expected = [1, 2, 3];

expect(curryRight(bind(fn, object), 3)('c')('b')('a')).toEqual(expected);
expect(curryRight(bind(fn, object), 3)('b', 'c')('a')).toEqual(expected);
expect(curryRight(bind(fn, object), 3)('a', 'b', 'c')).toEqual(expected);

expect(bind(curryRight(fn), object)('c')('b')('a')).toEqual(Array(3));
expect(bind(curryRight(fn), object)('b', 'c')('a')).toEqual(Array(3));
expect(bind(curryRight(fn), object)('a', 'b', 'c')).toEqual(expected);

object.curried = curryRight(fn);
expect(object.curried('c')('b')('a')).toEqual(Array(3));
expect(object.curried('b', 'c')('a')).toEqual(Array(3));
expect(object.curried('a', 'b', 'c')).toEqual(expected);
});

it('should work with partialed methods', () => {
const curried = curryRight(fn),
expected = [1, 2, 3, 4];

const a = partialRight(curried, 4),
b = partialRight(a, 3),
c = bind(b, null, 1),
d = partial(b(2), 1);

expect(c(2)).toEqual(expected);
expect(d()).toEqual(expected);
});

it(`\`curryRight\` should work for function names that shadow those on \`Object.prototype\``, () => {
const curried = curryRight(function hasOwnProperty(a: unknown, b: unknown) {
return [a, b];
});

expect(curried(2)(1)).toEqual([1, 2]);
});

it(`\`curryRight\` should work as an iteratee for methods like \`map\``, () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function fn(_a: unknown, _b: unknown) {
// eslint-disable-next-line prefer-rest-params
return Array.from(arguments);
}
const array = [fn, fn, fn];
// TODO test object like this
// const object = { 'a': fn, 'b': fn, 'c': fn };

[array].forEach(collection => {
const curries = collection.map(curryRight),
expected = collection.map(() => ['a', 'b']);

const actual = curries.map(curried => curried('b')('a'));

expect(actual).toEqual(expected);
});
});
});
Loading