Skip to content

Commit

Permalink
fix: Improve type inference for chain function (#370)
Browse files Browse the repository at this point in the history
  • Loading branch information
fResult authored Feb 21, 2024
1 parent 2d6c10a commit caf1a86
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 17 deletions.
8 changes: 5 additions & 3 deletions cdn/radash.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -486,9 +486,11 @@ const guard = (func, shouldGuard) => {
}
};

const chain = (...funcs) => (...args) => {
return funcs.slice(1).reduce((acc, fn) => fn(acc), funcs[0](...args));
};
function chain(...funcs) {
return function forInitialArg(initialArg) {
return funcs.reduce((acc, fn) => fn(acc), initialArg);
};
}
const compose = (...funcs) => {
return funcs.reverse().reduce((acc, fn) => fn(acc));
};
Expand Down
8 changes: 5 additions & 3 deletions cdn/radash.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,9 +489,11 @@ var radash = (function (exports) {
}
};

const chain = (...funcs) => (...args) => {
return funcs.slice(1).reduce((acc, fn) => fn(acc), funcs[0](...args));
};
function chain(...funcs) {
return function forInitialArg(initialArg) {
return funcs.reduce((acc, fn) => fn(acc), initialArg);
};
}
const compose = (...funcs) => {
return funcs.reverse().reduce((acc, fn) => fn(acc));
};
Expand Down
2 changes: 1 addition & 1 deletion cdn/radash.min.js

Large diffs are not rendered by default.

37 changes: 32 additions & 5 deletions docs/curry/chain.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,42 @@ description: Create a chain of function to run in order
## Basic usage

Chaining functions will cause them to execute one after another, passing the output from each function as the input to the next, returning the final output at the end of the chain.
```ts
import { chain } from 'radash'

const add = (y: number) => (x: number) => x + y
const mult = (y: number) => (x: number) => x * y
const addFive = add(5)
const double = mult(2)

const chained = chain(addFive, double)

chained(0) // => 10
chained(7) // => 24
```

### More example
```ts
import { chain } from 'radash'

const genesis = () => 0
const addFive = (num: number) => num + 5
const twoX = (num: number) => num * 2
type User = { id: number; name: string }
const users: User[] = [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'John Smith' },
{ id: 3, name: 'John Wick' }
]
function getName<T extends { name: string }>(item: T) {
return item.name;
}
function upperCase(text: string): Uppercase<string> {
return text.toUpperCase() as Uppercase<string>;
}

const chained = chain(genesis, addFive, twoX)
const getUpperName = chain<User, Uppercase<string>>(getName, upperCase)
// ^ Use chain function here

chained() // => 10
getUpperName(users[0]) // => 'JOHN DOE'
users.map((user) => getUpperName(user)) // => ['JOHN DOE', 'JOHN SMITH', 'JOHN WICK']
users.map(getUpperName) // => ['JOHN DOE', 'JOHN SMITH', 'JOHN WICK']
// ^ use chained function as a Point-free
```
51 changes: 47 additions & 4 deletions src/curry.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,55 @@
export type UnaryFunc<T, R> = (arg: T) => R
export type Func<TArgs = any, KReturn = any | void> = (
...args: TArgs[]
) => KReturn

export const chain =
(...funcs: Func[]) =>
(...args: any[]) => {
return funcs.slice(1).reduce((acc, fn) => fn(acc), funcs[0](...args))
export function chain<T1, T2, R>(
fn1: UnaryFunc<T1, T2>,
fn2: UnaryFunc<T2, R>
): UnaryFunc<T1, R>
export function chain<T1, T2, T3, R>(
fn1: UnaryFunc<T1, T2>,
fn2: UnaryFunc<T2, T3>,
fn3: UnaryFunc<T3, R>
): UnaryFunc<T1, R>
export function chain<T1, T2, T3, T4, R>(
fn1: UnaryFunc<T1, T2>,
fn2: UnaryFunc<T2, T3>,
fn3: UnaryFunc<T3, T4>,
fn4: UnaryFunc<T4, R>
): UnaryFunc<T1, R>
export function chain<T1, T2, T3, T4, T5, R>(
fn1: UnaryFunc<T1, T2>,
fn2: UnaryFunc<T2, T3>,
fn3: UnaryFunc<T3, T4>,
fn4: UnaryFunc<T4, T5>,
fn5: UnaryFunc<T5, R>
): UnaryFunc<T1, R>
export function chain<T1, T2, T3, T4, T5, T6, R>(
fn1: UnaryFunc<T1, T2>,
fn2: UnaryFunc<T2, T3>,
fn3: UnaryFunc<T3, T4>,
fn4: UnaryFunc<T4, T5>,
fn5: UnaryFunc<T5, T6>,
fn6: UnaryFunc<T6, R>
): UnaryFunc<T1, R>
export function chain<T1, T2, T3, T4, T5, T6, T7, R>(
fn1: UnaryFunc<T1, T2>,
fn2: UnaryFunc<T2, T3>,
fn3: UnaryFunc<T3, T4>,
fn4: UnaryFunc<T4, T5>,
fn5: UnaryFunc<T5, T6>,
fn6: UnaryFunc<T6, T7>,
fn7: UnaryFunc<T7, R>
): UnaryFunc<T1, R>
export function chain<T = any, R = any>(
...fns: ((arg: any) => any)[]
): UnaryFunc<T, R>
export function chain(...funcs: Func[]): Func {
return function forInitialArg(initialArg: Parameters<Func>[0]) {
return funcs.reduce((acc, fn) => fn(acc), initialArg)
}
}

export const compose = (...funcs: Func[]) => {
return funcs.reverse().reduce((acc, fn) => fn(acc))
Expand Down
47 changes: 46 additions & 1 deletion src/tests/curry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,54 @@ describe('curry module', () => {
const addFive = (num: number) => num + 5
const twoX = (num: number) => num * 2
const func = _.chain(genesis, addFive, twoX)
const result = func()
const result = func(0)
assert.equal(result, 10)
})

test('calls add(1), then addFive, then twoX functions by 1', () => {
const add = (y: number) => (x: number) => x + y
const addFive = add(5)
const twoX = (num: number) => num * 2
const func = _.chain(add(1), addFive, twoX)
const result = func(1)
assert.equal(result, 14)
})

test('calls add(2), then addFive, then twoX, then repeatX functions by 1', () => {
const add = (y: number) => (x: number) => x + y
const addFive = add(5)
const twoX = (num: number) => num * 2
const repeatX = (num: number) => 'X'.repeat(num)
const func = _.chain(add(2), addFive, twoX, repeatX)
const result = func(1)
assert.equal(result, 'XXXXXXXXXXXXXXXX')
})

test('calls addFive, then add(2), then twoX, then repeatX functions by 1', () => {
const add = (y: number) => (x: number) => x + y
const addFive = add(5)
const twoX = (num: number) => num * 2
const repeatX = (num: number) => 'X'.repeat(num)
const func = _.chain(addFive, add(2), twoX, repeatX)
const result = func(1)
assert.equal(result, 'XXXXXXXXXXXXXXXX')
})

test('calls getName, then upperCase functions as a mapper for User[]', () => {
type User = { id: number; name: string }
const users: User[] = [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'John Smith' },
{ id: 3, name: 'John Wick' }
]
const getName = <T extends { name: string }>(item: T) => item.name
const upperCase: (x: string) => Uppercase<string> = (text: string) =>
text.toUpperCase() as Uppercase<string>

const getUpperName = _.chain<User, Uppercase<string>>(getName, upperCase)
const result = users.map(getUpperName)
assert.deepEqual(result, ['JOHN DOE', 'JOHN SMITH', 'JOHN WICK'])
})
})

describe('proxied function', () => {
Expand Down

0 comments on commit caf1a86

Please sign in to comment.