Does it work for you ?
type Foo = typeof foo
type Bar = typeof bar
type Baz = typeof baz
type Fn = (a: any) => any
type Head<T extends any[]> =
T extends [infer H, ...infer _]
? H
: never;
type Last<T extends any[]> =
T extends [infer _]
? never : T extends [...infer _, infer Tl]
? Tl
: never;
// credits goes to https://mcmap.net/q/436715/-typescript-check-for-the-39-any-39-type
type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
type IsAny<T> = IfAny<T, true, never>;
type HandleAny<T extends Fn, U> =
IsAny<Head<Parameters<T>>> extends true ?
(a: U) => ReturnType<T>
: T
type Allowed<
T extends Fn[],
Cache extends Fn[] = []
> =
T extends []
? Cache
: T extends [infer Lst]
? Lst extends Fn
? Allowed<[], [...Cache, Lst]> : never
: T extends [infer Fst, ...infer Lst]
? Fst extends Fn
? Lst extends Fn[]
? Head<Lst> extends Fn
? Head<Parameters<Fst>> extends ReturnType<Head<Lst>>
? Allowed<Lst, [...Cache, HandleAny<Fst, ReturnType<Head<Lst>>>]>
: never
: never
: never
: never
: never;
type LastParameterOf<T extends Fn[]> =
Last<T> extends Fn
? Head<Parameters<Last<T>>>
: never
type Return<T extends Fn[]> =
Head<T> extends Fn
? ReturnType<Head<T>>
: never
function compose<T extends Fn, Fns extends T[], Allow extends {
0: [never],
1: [LastParameterOf<Fns>]
}[Allowed<Fns> extends never ? 0 : 1]>
(...args: [...Fns] & Allowed<Fns>): (...data: Allow) => Return<Fns>
function compose<
T extends Fn,
Fns extends T[], Allow extends unknown[]
>(...args: [...Fns]) {
return (...data: Allow) =>
args.reduceRight((acc, elem) => elem(acc), data)
}
const foo = (arg: 1 | 2) => [1, 2, 3]
const bar = (arg: string) => arg.length > 10 ? 1 : 2
const baz = (arg: number[]) => 'hello'
/**
* Ok, but you need explicitly add allowed type
*/
const check = compose((a: string) => a, baz)([1, 2, 3]) // [number]
/**
* Errors
*/
// error because no type
const check_ = compose((a) => a, baz)([1, 2, 3])
// error because `a` expected to be string instead of number
const check__ = compose((a: number) => a, baz)([1, 2, 3])
Playground
Here, in my blog, you can find an explanation. Let me know if you are still interested in this question, I will try to provide more examplations or examples.
<S, R, Fns extends readonly ((source: any) => any)[]>(source: S, ...fns: [(source: S) => any, ...Fns, (source: any) => R]) => R
. That isfns
is a variadic tuple that starts withS => any
and ends withany => R
. I also had to// @ts-ignore
the return value. – Ekaterinapipe
also uses overloads: github.com/gcanti/fp-ts/blob/master/src/function.ts#L315 – Silverplate