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:
D-Sketon 2024-08-11 09:53:02 +08:00 committed by GitHub
parent 2a3013597c
commit 5260d5b81b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 510 additions and 0 deletions

View 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);
});
});

View 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);
});
});

View File

@ -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' },
],
},
{

View File

@ -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' },
],
},
{

View 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'
```

View 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'
```

View 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'
```

View 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'
```

View File

@ -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';

View 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
View 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;

View 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');
});
});

View 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;