Typescript 减少函数数组

问题描述 投票:0回答:2

假设我有一个函数数组,其中每个函数都接受前一个函数的返回值,并且我在该函数上调用

Array#reduce
,并使用数组中第一个函数接受的初始值。这是完全合理的,我希望返回类型是最后一个函数的返回类型。

但是 TypeScript 不允许我这样做(参见 Playground)。

更务实的是,我正在尝试编写一个通用的

pipe
函数,它将组合作为
...rest
给出的函数,并将第一个参数“管道”到组合函数中:

function pipe(source, ...fns) {
  return fns.reduce((value, fn) => fn(value), source);
}

我根本找不到一种方法来输入它,即使使用 varadic 元组类型

即使我尝试递归地写出该函数,我也不太确定如何输入它:

function pipe<
  S,
  R,
  Fns extends readonly unknown[],
>(source: S, ...fns: [(source: S) => R, ...Fns]): R {
  if (fns.length === 0) {
    return source;
  }

  const [fn, ...rest] = fns;

  return pipe(fn(source), rest);
}

看游乐场

typescript functional-programming variadic-tuple-types
2个回答
3
投票

它对你有用吗?

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://stackoverflow.com/questions/55541275/typescript-check-for-the-any-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])

游乐场

在这里,在我的博客中,您可以找到解释。如果您仍然对这个问题感兴趣,请告诉我,我会尽力提供更多示例或示例。


0
投票

发布答案,因为链接不适合评论:

type Last<T, Else = never> = T extends [...infer _, infer B] ? B : Else;

type Pipeline<
  A,
  Fns extends Array<(a: any) => any>,
  Acc extends Array<(a: any) => unknown> = [],
> =
  Fns extends [(a: A) => infer B, ...infer Tail]
  ? Tail extends Array<(a: any) => any>
    ? Pipeline<B, Tail, [...Acc, (a: A) => B]>
    : never
  : Acc


export default function pipe<
  S,
  Fns extends Array<(a: any) => any>,
>(
  source: S,
  ...fns: Pipeline<S, Fns> extends Fns ? Fns : Pipeline<S, Fns>
): Fns extends [] ? S : ReturnType<Last<Fns>> {
  // @ts-ignore
  return fns.reduce((value, fn) => fn(value), source);
}

游乐场链接

© www.soinside.com 2019 - 2024. All rights reserved.