mirror of
https://github.com/toss/es-toolkit.git
synced 2024-11-24 03:32:58 +03:00
feat(partial, partialRight): Implement partial/partialRight (#368)
* feat(partial/partialRight): implenent partial/partialRight * Apply suggestions from code review --------- Co-authored-by: Sojin Park <raon0211@gmail.com>
This commit is contained in:
parent
2a3013597c
commit
5260d5b81b
27
benchmarks/performance/partial.bench.ts
Normal file
27
benchmarks/performance/partial.bench.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { partial as partialToolkit } from 'es-toolkit';
|
||||
import { partial as partialLodash } from 'lodash';
|
||||
|
||||
const fn = function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
return Array.from(arguments);
|
||||
};
|
||||
|
||||
describe('partial', () => {
|
||||
bench('es-toolkit/partial - without placeholder', () => {
|
||||
partialToolkit(fn, 'a');
|
||||
});
|
||||
|
||||
bench('lodash/partial - without placeholder', () => {
|
||||
partialLodash(fn, 'a');
|
||||
});
|
||||
bench('es-toolkit/partial - with placeholder', () => {
|
||||
const { placeholder } = partialToolkit;
|
||||
partialToolkit(fn, placeholder, 'b', placeholder);
|
||||
});
|
||||
|
||||
bench('lodash/partial - with placeholder', () => {
|
||||
const { placeholder } = partialLodash;
|
||||
partialLodash(fn, placeholder, 'b', placeholder);
|
||||
});
|
||||
});
|
27
benchmarks/performance/partialRight.bench.ts
Normal file
27
benchmarks/performance/partialRight.bench.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { partialRight as partialRightToolkit } from 'es-toolkit';
|
||||
import { partialRight as partialRightLodash } from 'lodash';
|
||||
|
||||
const fn = function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
return Array.from(arguments);
|
||||
};
|
||||
|
||||
describe('partial', () => {
|
||||
bench('es-toolkit/partialRight - without placeholder', () => {
|
||||
partialRightToolkit(fn, 'a');
|
||||
});
|
||||
|
||||
bench('lodash/partialRight - without placeholder', () => {
|
||||
partialRightLodash(fn, 'a');
|
||||
});
|
||||
bench('es-toolkit/partialRight - with placeholder', () => {
|
||||
const { placeholder } = partialRightToolkit;
|
||||
partialRightToolkit(fn, placeholder, 'b', placeholder);
|
||||
});
|
||||
|
||||
bench('lodash/partialRight - with placeholder', () => {
|
||||
const { placeholder } = partialRightLodash;
|
||||
partialRightLodash(fn, placeholder, 'b', placeholder);
|
||||
});
|
||||
});
|
@ -123,6 +123,8 @@ function sidebar(): DefaultTheme.Sidebar {
|
||||
{ text: 'ary', link: '/reference/function/ary' },
|
||||
{ text: 'unary', link: '/reference/function/unary' },
|
||||
{ text: 'bind (compat)', link: '/reference/compat/function/bind' },
|
||||
{ text: 'partial', link: '/reference/function/partial' },
|
||||
{ text: 'partialRight', link: '/reference/function/partialRight' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -119,6 +119,8 @@ function sidebar(): DefaultTheme.Sidebar {
|
||||
{ text: 'ary', link: '/zh_hans/reference/function/ary' },
|
||||
{ text: 'unary', link: '/zh_hans/reference/function/unary' },
|
||||
{ text: 'bind (兼容性)', link: '/zh_hans/reference/compat/function/bind' },
|
||||
{ text: 'partial', link: '/zh_hans/reference/function/partial' },
|
||||
{ text: 'partialRight', link: '/zh_hans/reference/function/partialRight' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
45
docs/reference/function/partial.md
Normal file
45
docs/reference/function/partial.md
Normal file
@ -0,0 +1,45 @@
|
||||
# partial
|
||||
|
||||
Creates a function that invokes `func` with `partialArgs` prepended to the arguments it receives. This method is like `bind` except it does not alter the `this` binding.
|
||||
|
||||
The `partial.placeholder` value, which defaults to a `symbol`, may be used as a placeholder for partially applied arguments.
|
||||
|
||||
Note: This method doesn't set the `length` property of partially applied functions.
|
||||
|
||||
## Signature
|
||||
|
||||
```typescript
|
||||
function partial<F extends Function>(func: F, ...partialArgs: any[]): F;
|
||||
|
||||
namespace partial {
|
||||
placeholder: symbol;
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
- `func` (`F`): The function to partially apply arguments to.
|
||||
- `partialArgs` (`any[]`, optional): The arguments to be partially applied.
|
||||
|
||||
### Returns
|
||||
|
||||
(`F`): Returns the new partially applied function.
|
||||
|
||||
## Examples
|
||||
|
||||
```typescript
|
||||
import { partial } from 'es-toolkit/function';
|
||||
|
||||
function greet(greeting, name) {
|
||||
return greeting + ' ' + name;
|
||||
}
|
||||
|
||||
const sayHelloTo = partial(greet, 'hello');
|
||||
sayHelloTo('fred');
|
||||
// => 'hello fred'
|
||||
|
||||
// Partially applied with placeholders.
|
||||
const greetFred = partial(greet, partial.placeholder, 'fred');
|
||||
greetFred('hi');
|
||||
// => 'hi fred'
|
||||
```
|
45
docs/reference/function/partialRight.md
Normal file
45
docs/reference/function/partialRight.md
Normal file
@ -0,0 +1,45 @@
|
||||
# partialRight
|
||||
|
||||
This method is like `partial` except that partially applied arguments are appended to the arguments it receives.
|
||||
|
||||
The `partialRight.placeholder` value, which defaults to a `symbol`, may be used as a placeholder for partially applied arguments.
|
||||
|
||||
Note: This method doesn't set the `length` property of partially applied functions.
|
||||
|
||||
## Signature
|
||||
|
||||
```typescript
|
||||
function partialRight<F extends Function>(func: F, ...partialArgs: any[]): F;
|
||||
|
||||
namespace partialRight {
|
||||
placeholder: symbol;
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
- `func` (`F`): The function to partially apply arguments to.
|
||||
- `partialArgs` (`any[]`, optional): The arguments to be partially applied.
|
||||
|
||||
### Returns
|
||||
|
||||
(`F`): Returns the new partially applied function.
|
||||
|
||||
## Examples
|
||||
|
||||
```typescript
|
||||
import { partialRight } from 'es-toolkit/function';
|
||||
|
||||
function greet(greeting, name) {
|
||||
return greeting + ' ' + name;
|
||||
}
|
||||
|
||||
const greetFred = partialRight(greet, 'fred');
|
||||
greetFred('hi');
|
||||
// => 'hi fred'
|
||||
|
||||
// Partially applied with placeholders.
|
||||
const sayHelloTo = partialRight(greet, 'hello', partialRight.placeholder);
|
||||
sayHelloTo('fred');
|
||||
// => 'hello fred'
|
||||
```
|
45
docs/zh_hans/reference/function/partial.md
Normal file
45
docs/zh_hans/reference/function/partial.md
Normal file
@ -0,0 +1,45 @@
|
||||
# partial
|
||||
|
||||
创建一个函数,该函数会在调用时将 `partialArgs` 作为前置参数传递给 `func`。此方法类似于 `bind`,但不会改变 `this` 绑定。
|
||||
|
||||
`partial.placeholder` 的值默认是一个 `symbol`,可以用作附加的部分参数的占位符。
|
||||
|
||||
**注意:** 这个方法不会设置部分应用函数的 `length` 属性。
|
||||
|
||||
## 签名
|
||||
|
||||
```typescript
|
||||
function partial<F extends Function>(func: F, ...partialArgs: any[]): F;
|
||||
|
||||
namespace partial {
|
||||
placeholder: symbol;
|
||||
}
|
||||
```
|
||||
|
||||
### 参数
|
||||
|
||||
- `func` (`F`): 要部分应用的原始函数。
|
||||
- `partialArgs` (`any[]`, 可选): 附加的部分参数。
|
||||
|
||||
### 返回值
|
||||
|
||||
(`F`): 返回新的部分应用函数。
|
||||
|
||||
## 示例
|
||||
|
||||
```typescript
|
||||
import { partial } from 'es-toolkit/function';
|
||||
|
||||
function greet(greeting, name) {
|
||||
return greeting + ' ' + name;
|
||||
}
|
||||
|
||||
const sayHelloTo = partial(greet, 'hello');
|
||||
sayHelloTo('fred');
|
||||
// => 'hello fred'
|
||||
|
||||
// Partially applied with placeholders.
|
||||
const greetFred = partial(greet, partial.placeholder, 'fred');
|
||||
greetFred('hi');
|
||||
// => 'hi fred'
|
||||
```
|
45
docs/zh_hans/reference/function/partialRight.md
Normal file
45
docs/zh_hans/reference/function/partialRight.md
Normal file
@ -0,0 +1,45 @@
|
||||
# partialRight
|
||||
|
||||
此方法类似于 `partial`,但部分应用的参数会被附加到接收到的参数之后。
|
||||
|
||||
`partialRight.placeholder` 的值默认是一个 `symbol`,可以用作附加的部分参数的占位符。
|
||||
|
||||
**注意:** 这个方法不会设置部分应用函数的 `length` 属性。
|
||||
|
||||
## 签名
|
||||
|
||||
```typescript
|
||||
function partialRight<F extends Function>(func: F, ...partialArgs: any[]): F;
|
||||
|
||||
namespace partialRight {
|
||||
placeholder: symbol;
|
||||
}
|
||||
```
|
||||
|
||||
### 参数
|
||||
|
||||
- `func` (`F`): 要部分应用的原始函数。
|
||||
- `partialArgs` (`any[]`, 可选): 附加的部分参数。
|
||||
|
||||
### 返回值
|
||||
|
||||
(`F`): 返回新的部分应用函数。
|
||||
|
||||
## 示例
|
||||
|
||||
```typescript
|
||||
import { partialRight } from 'es-toolkit/function';
|
||||
|
||||
function greet(greeting, name) {
|
||||
return greeting + ' ' + name;
|
||||
}
|
||||
|
||||
const greetFred = partialRight(greet, 'fred');
|
||||
greetFred('hi');
|
||||
// => 'hi fred'
|
||||
|
||||
// Partially applied with placeholders.
|
||||
const sayHelloTo = partialRight(greet, 'hello', partialRight.placeholder);
|
||||
sayHelloTo('fred');
|
||||
// => 'hello fred'
|
||||
```
|
@ -7,3 +7,5 @@ export { throttle } from './throttle.ts';
|
||||
export { negate } from './negate.ts';
|
||||
export { ary } from './ary.ts';
|
||||
export { unary } from './unary.ts';
|
||||
export { partial } from './partial.ts';
|
||||
export { partialRight } from './partialRight.ts';
|
||||
|
84
src/function/partial.spec.ts
Normal file
84
src/function/partial.spec.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { partial } from './partial';
|
||||
|
||||
function identity(arg?: any): any {
|
||||
return arg;
|
||||
}
|
||||
|
||||
describe('partial', () => {
|
||||
const { placeholder } = partial;
|
||||
it('partial partially applies arguments', () => {
|
||||
const par = partial(identity, 'a');
|
||||
expect(par()).toBe('a');
|
||||
});
|
||||
|
||||
it('partial creates a function that can be invoked with additional arguments', () => {
|
||||
const fn = function (a?: string, b?: string) {
|
||||
return [a, b];
|
||||
};
|
||||
const par = partial(fn, 'a');
|
||||
expect(par('b')).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('partial works when there are no partially applied arguments and the created function is invoked without additional arguments', () => {
|
||||
const fn = function () {
|
||||
return arguments.length;
|
||||
};
|
||||
const par = partial(fn);
|
||||
expect(par()).toBe(0);
|
||||
});
|
||||
|
||||
it('partial works when there are no partially applied arguments and the created function is invoked with additional arguments', () => {
|
||||
const par = partial(identity);
|
||||
expect(par('a')).toBe('a');
|
||||
});
|
||||
|
||||
it('partial supports placeholders', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const fn = function (..._: any[]) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
return Array.from(arguments);
|
||||
};
|
||||
const par = partial(fn, placeholder, 'b', placeholder);
|
||||
expect(par('a', 'c')).toEqual(['a', 'b', 'c']);
|
||||
expect(par('a')).toEqual(['a', 'b', undefined]);
|
||||
expect(par()).toEqual([undefined, 'b', undefined]);
|
||||
|
||||
expect(par('a', 'c', 'd')).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
it('partial creates a function with a length of 0', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const fn = function (_a: string, _b: string, _c: string) {};
|
||||
const par = partial(fn, 'a');
|
||||
expect(par.length).toBe(0);
|
||||
});
|
||||
|
||||
it('partial ensures new par is an instance of func', () => {
|
||||
function Foo(value: unknown) {
|
||||
return value && object;
|
||||
}
|
||||
|
||||
const object = {};
|
||||
const par = partial(Foo);
|
||||
|
||||
// @ts-expect-error - par is a constructor
|
||||
expect(new par() instanceof Foo);
|
||||
// @ts-expect-error - par is a constructor
|
||||
expect(new par(true)).toBe(object);
|
||||
});
|
||||
|
||||
it('partial clones metadata for created functions', () => {
|
||||
function greet(greeting?: string, name?: string) {
|
||||
return `${greeting} ${name}`;
|
||||
}
|
||||
|
||||
const par1 = partial(greet, 'hi');
|
||||
const par2 = partial(par1, 'barney');
|
||||
const par3 = partial(par1, 'pebbles');
|
||||
|
||||
expect(par1('fred')).toBe('hi fred');
|
||||
expect(par2()).toBe('hi barney');
|
||||
expect(par3()).toBe('hi pebbles');
|
||||
});
|
||||
});
|
50
src/function/partial.ts
Normal file
50
src/function/partial.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Creates a function that invokes `func` with `partialArgs` prepended to the arguments it receives. This method is like `bind` except it does not alter the `this` binding.
|
||||
*
|
||||
* The partial.placeholder value, which defaults to a `symbol`, may be used as a placeholder for partially applied arguments.
|
||||
*
|
||||
* Note: This method doesn't set the `length` property of partially applied functions.
|
||||
*
|
||||
* @template F The type of the function to partially apply.
|
||||
* @param {F} func The function to partially apply arguments to.
|
||||
* @param {any[]} partialArgs The arguments to be partially applied.
|
||||
* @returns {F} Returns the new partially applied function.
|
||||
*
|
||||
* @example
|
||||
* function greet(greeting, name) {
|
||||
* return greeting + ' ' + name;
|
||||
* }
|
||||
*
|
||||
* const sayHelloTo = partial(greet, 'hello');
|
||||
* sayHelloTo('fred');
|
||||
* // => 'hello fred'
|
||||
*
|
||||
* // Partially applied with placeholders.
|
||||
* const greetFred = partial(greet, partial.placeholder, 'fred');
|
||||
* greetFred('hi');
|
||||
* // => 'hi fred'
|
||||
*/
|
||||
export function partial<F extends (...args: any[]) => any>(func: F, ...partialArgs: any[]): F {
|
||||
return function (this: any, ...providedArgs: any[]) {
|
||||
const args: any[] = [];
|
||||
|
||||
let startIndex = 0;
|
||||
for (let i = 0; i < partialArgs.length; i++) {
|
||||
const arg = partialArgs[i];
|
||||
|
||||
if (arg === partial.placeholder) {
|
||||
args.push(providedArgs[startIndex++]);
|
||||
} else {
|
||||
args.push(arg);
|
||||
}
|
||||
}
|
||||
for (let i = startIndex; i < providedArgs.length; i++) {
|
||||
args.push(providedArgs[i]);
|
||||
}
|
||||
|
||||
return func.apply(this, args);
|
||||
} as any as F;
|
||||
}
|
||||
|
||||
const partialPlaceholder: unique symbol = Symbol('partial.placeholder');
|
||||
partial.placeholder = partialPlaceholder;
|
85
src/function/partialRight.spec.ts
Normal file
85
src/function/partialRight.spec.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { partialRight } from './partialRight';
|
||||
|
||||
function identity(arg?: any): any {
|
||||
return arg;
|
||||
}
|
||||
|
||||
describe('partialRight', () => {
|
||||
const { placeholder } = partialRight;
|
||||
it('partialRight partially applies arguments', () => {
|
||||
const par = partialRight(identity, 'a');
|
||||
expect(par()).toBe('a');
|
||||
});
|
||||
|
||||
it('partialRight creates a function that can be invoked with additional arguments', () => {
|
||||
const fn = function (a?: string, b?: string) {
|
||||
return [a, b];
|
||||
};
|
||||
const par = partialRight(fn, 'a');
|
||||
expect(par('b')).toEqual(['b', 'a']);
|
||||
});
|
||||
|
||||
it('partialRight works when there are no partially applied arguments and the created function is invoked without additional arguments', () => {
|
||||
const fn = function () {
|
||||
return arguments.length;
|
||||
};
|
||||
const par = partialRight(fn);
|
||||
expect(par()).toBe(0);
|
||||
});
|
||||
|
||||
it('partialRight works when there are no partially applied arguments and the created function is invoked with additional arguments', () => {
|
||||
const par = partialRight(identity);
|
||||
expect(par('a')).toBe('a');
|
||||
});
|
||||
|
||||
it('partialRight supports placeholders', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const fn = function (..._: any[]) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
return Array.from(arguments);
|
||||
};
|
||||
let par = partialRight(fn, placeholder, 'b', placeholder);
|
||||
expect(par('a', 'c')).toEqual(['a', 'b', 'c']);
|
||||
expect(par('a')).toEqual(['a', 'b', undefined]);
|
||||
expect(par()).toEqual([undefined, 'b', undefined]);
|
||||
|
||||
par = partialRight(fn, placeholder, 'c', placeholder);
|
||||
expect(par('a', 'b', 'd')).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
it('partialRight creates a function with a length of 0', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const fn = function (_a: string, _b: string, _c: string) {};
|
||||
const par = partialRight(fn, 'a');
|
||||
expect(par.length).toBe(0);
|
||||
});
|
||||
|
||||
it('partialRight ensures new par is an instance of func', () => {
|
||||
function Foo(value: unknown) {
|
||||
return value && object;
|
||||
}
|
||||
|
||||
const object = {};
|
||||
const par = partialRight(Foo);
|
||||
|
||||
// @ts-expect-error - par is a constructor
|
||||
expect(new par() instanceof Foo);
|
||||
// @ts-expect-error - par is a constructor
|
||||
expect(new par(true)).toBe(object);
|
||||
});
|
||||
|
||||
it('partialRight clones metadata for created functions', () => {
|
||||
function greet(greeting?: string, name?: string) {
|
||||
return `${greeting} ${name}`;
|
||||
}
|
||||
|
||||
const par1 = partialRight(greet, 'hi');
|
||||
const par2 = partialRight(par1, 'barney');
|
||||
const par3 = partialRight(par1, 'pebbles');
|
||||
|
||||
expect(par1('fred')).toBe('fred hi');
|
||||
expect(par2()).toBe('barney hi');
|
||||
expect(par3()).toBe('pebbles hi');
|
||||
});
|
||||
});
|
51
src/function/partialRight.ts
Normal file
51
src/function/partialRight.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* This method is like `partial` except that partially applied arguments are appended to the arguments it receives.
|
||||
*
|
||||
* The partialRight.placeholder value, which defaults to a `symbol`, may be used as a placeholder for partially applied arguments.
|
||||
*
|
||||
* Note: This method doesn't set the `length` property of partially applied functions.
|
||||
*
|
||||
* @template F The type of the function to partially apply.
|
||||
* @param {F} func The function to partially apply arguments to.
|
||||
* @param {any[]} partialArgs The arguments to be partially applied.
|
||||
* @returns {F} Returns the new partially applied function.
|
||||
*
|
||||
* @example
|
||||
* function greet(greeting, name) {
|
||||
* return greeting + ' ' + name;
|
||||
* }
|
||||
*
|
||||
* const greetFred = partialRight(greet, 'fred');
|
||||
* greetFred('hi');
|
||||
* // => 'hi fred'
|
||||
*
|
||||
* // Partially applied with placeholders.
|
||||
* const sayHelloTo = partialRight(greet, 'hello', partialRight.placeholder);
|
||||
* sayHelloTo('fred');
|
||||
* // => 'hello fred'
|
||||
*/
|
||||
export function partialRight<F extends (...args: any[]) => any>(func: F, ...partialArgs: any[]): F {
|
||||
return function (this: any, ...providedArgs: any[]) {
|
||||
const placeholderLength = partialArgs.filter(arg => arg === partialRightPlaceholder).length;
|
||||
const rangeLength = Math.max(providedArgs.length - placeholderLength, 0);
|
||||
const args: any[] = [];
|
||||
|
||||
let providedIndex = 0;
|
||||
for (let i = 0; i < rangeLength; i++) {
|
||||
args.push(providedArgs[providedIndex++]);
|
||||
}
|
||||
for (let i = 0; i < partialArgs.length; i++) {
|
||||
const arg = partialArgs[i];
|
||||
|
||||
if (arg === partialRight.placeholder) {
|
||||
args.push(providedArgs[providedIndex++]);
|
||||
} else {
|
||||
args.push(arg);
|
||||
}
|
||||
}
|
||||
return func.apply(this, args);
|
||||
} as any as F;
|
||||
}
|
||||
|
||||
const partialRightPlaceholder: unique symbol = Symbol('partialRight.placeholder');
|
||||
partialRight.placeholder = partialRightPlaceholder;
|
Loading…
Reference in New Issue
Block a user