【问题标题】:TypeScript Safe Function CompositionTypeScript 安全函数组合
【发布时间】:2020-06-15 08:35:24
【问题描述】:

这段代码来自很棒的文章Introducing The Recursive Pipe and Compose Types。

它适用于 typescript@3.4.5,但不适用于当前最新版本 3.8.3。

为了正确编译,代码应该拼接成两个文件。

//file Types.ts
export type ExtractFunctionArguments<Fn> = Fn extends (...args: infer P) => any
  ? P
  : never;

export type ExtractFunctionReturnValue<Fn> = Fn extends (
  ...args: any[]
) => infer P
  ? P
  : never;

type BooleanSwitch<Test, T = true, F = false> = Test extends true ? T : F;

export type AnyFunction = (...args: any[]) => any;

export type AnyFunction1 = (a: any) => any;

type Arbitrary = "It is now 1554792354 seconds since since Jan 01, 1970";

type IsAny<O, T = true, F = false> = Arbitrary extends O
  ? any extends O
    ? T
    : F
  : F;

export type Pipe<
  Fns extends any[],
  IsPipe = true,
  PreviousFunction = void,
  InitialParams extends any[] = any[],
  ReturnType = any
> = {
  next: ((..._: Fns) => any) extends (_: infer First, ..._1: infer Next) => any
    ? PreviousFunction extends void
      ? Pipe<
          Next,
          IsPipe,
          First,
          ExtractFunctionArguments<First>,
          ExtractFunctionReturnValue<First>
        >
      : ReturnType extends ExtractFunctionArguments<First>[0]
      ? Pipe<
          Next,
          IsPipe,
          First,
          InitialParams,
          ExtractFunctionReturnValue<First>
        >
      : IsAny<ReturnType> extends true
      ? Pipe<
          Next,
          IsPipe,
          First,
          InitialParams,
          ExtractFunctionReturnValue<First>
        >
      : {
          ERROR: [
            "Return type ",
            ReturnType,
            "does comply with the input of",
            ExtractFunctionArguments<First>[0]
          ];
          POSITION: [
            "Position of problem for input arguments is at",
            Fns["length"],
            "from the",
            BooleanSwitch<IsPipe, "end", "beginning">,
            "and the output of function to the ",
            BooleanSwitch<IsPipe, "left", "right">
          ];
        }
    : never;
  done: (...args: InitialParams) => ReturnType;
}[Fns extends [] ? "done" : "next"];

export type PipeFn = <Fns extends [AnyFunction, ...AnyFunction1[]]>(
  ...fns: Fns & Pipe<Fns> extends AnyFunction ? Fns : never
) => Pipe<Fns>;

export type PipelineFn = <
  Arg,
  Fns extends [(arg: Arg) => any, ...AnyFunction1[]]
>(
  arg: Arg,
  ...fns: Fns & Pipe<Fns> extends AnyFunction ? Fns : never
) => ExtractFunctionReturnValue<Pipe<Fns>> extends {
  ERROR: [string];
  POSITION: [string];
}
  ? Pipe<Fns>
  : ExtractFunctionReturnValue<Pipe<Fns>>;


//file Scenario.ts

import { PipeFn, AnyFunction, AnyFunction1 } from "./Types";

export const pipe: PipeFn = (entry: AnyFunction, ...funcs: AnyFunction1[]) => (
  ...arg: unknown[]
) => funcs.reduce((acc, item) => item.call(item, acc), entry(...arg)); // Compile time error in TypeScript@3.8.3

const add = (x: number, y: number) => x + y;
const inc = (x: number) => add(1, x);
const convertNumberToString = (x: NonNullable<number>) => x.toString();

let pipedFunc = pipe(add, inc, convertNumberToString);

问题

  • 为什么 de code 在当前版本的 TypeScript 中无法编译,有什么解决方法吗?
  • 还有其他更安全的方法来确保 TypeScript 中的函数组合吗?
  • 有关如何调试此类错误的任何提示?

编译器错误 typescript@3.8.3

npx tsc Scenario.ts --noErrorTruncation

Scenario.ts(3,29): error TS2322: Type '(entry: AnyFunction, ...funcs: AnyFunction1[]) => (...arg: unknown[]) => any' is not assignable to type 'PipeFn'.
  Type '(...arg: unknown[]) => any' is not assignable to type '{ next: (..._: Fns) => any extends (_: infer First, ..._1: infer Next) => any ? { next: (..._: Next) => any extends (_: infer First, ..._1: infer Next) => any ? First extends void ? any[Next extends [] ? "done" : "next"] : ExtractFunctionReturnValue<First> extends ExtractFunctionArguments<First>[0] ? any[Next extends [] ? "done" : "next"] : IsAny<ExtractFunctionReturnValue<First>, true, false> extends true ? any[Next extends [] ? "done" : "next"] : { ERROR: ["Return type ", ExtractFunctionReturnValue<First>, "does comply with the input of", ExtractFunctionArguments<First>[0]]; POSITION: ["Position of problem for input arguments is at", Next["length"], "from the", "end", "and the output of function to the ", "left"]; } : never; done: (...args: ExtractFunctionArguments<First>) => ExtractFunctionReturnValue<First>; }[Next extends [] ? "done" : "next"] : never; done: (...args: any[]) => any; }[Fns extends [] ? "done" : "next"]'.
    Type '(...arg: unknown[]) => any' is not assignable to type '(..._: Fns) => any extends (_: infer First, ..._1: infer Next) => any ? { next: (..._: Next) => any extends (_: infer First, ..._1: infer Next) => any ? First extends void ? any[Next extends [] ? "done" : "next"] : ExtractFunctionReturnValue<First> extends ExtractFunctionArguments<First>[0] ? any[Next extends [] ? "done" : "next"] : IsAny<ExtractFunctionReturnValue<First>, true, false> extends true ? any[Next extends [] ? "done" : "next"] : { ERROR: ["Return type ", ExtractFunctionReturnValue<First>, "does comply with the input of", ExtractFunctionArguments<First>[0]]; POSITION: ["Position of problem for input arguments is at", Next["length"], "from the", "end", "and the output of function to the ", "left"]; } : never; done: (...args: ExtractFunctionArguments<First>) => ExtractFunctionReturnValue<First>; }[Next extends [] ? "done" : "next"] : never'.

【问题讨论】:

    标签: typescript generics typescript-generics


    【解决方案1】:

    无法回答以下问题。

    为什么 de code 在当前版本的 TypeScript 中无法编译

    但可以使用ts-toolbelt 库实现替代解决方案。 Repl.it.

    import { F, L, B, A, I, O } from "ts-toolbelt";
    
    export type ListOfAnyFunction = ((...args: any[]) => any)[];
    
    type CastToTuple<T> = A.Cast<T, [T]>;
    
    type GetResultType<
      Fns extends ((...args: unknown[]) => unknown)[] = [],
      I extends I.Iteration = I.IterationOf<"0">
    > = {
      0: A.Extends<L.Length<L.Tail<Fns>>, I.Pos<I>> extends 1
        ? GetResultType<Fns, I.Next<I>>
        : A.Extends<
            CastToTuple<F.Return<Fns[I.Pos<I>]>>,
            F.Parameters<Fns[I.Pos<I.Next<I>>]>
          > extends 1
        ? GetResultType<Fns, I.Next<I>>
        : {
            ERROR: "Functions are Incompatible...";
            Message: `Return of function index '@Position', is is not a valid (@FirstSignature) as a parameter for (@NextSignature)`;
            Position: I.Pos<I>;
            FirstSignature: Fns[I.Pos<I>];
            NextSignature: Fns[I.Pos<I.Next<I>>];
          };
      1: F.Return<L.Last<Fns>>;
    }[A.Extends<L.Length<Fns>, I.Pos<I>>];
    
    type Pipe = <Fns extends ListOfAnyFunction>(
      ...fns: GetResultType<Fns> extends infer Result
        ? Result extends { ERROR: string }
          ? Result
          : Fns
        : never
    ) => (...args: Parameters<L.Head<Fns>>) => GetResultType<Fns>;
    
    export const pipe: Pipe = <Fns extends ListOfAnyFunction>(...funcs: Fns) => (
      ...arg: unknown[]
    ) => {
      const entry = funcs[0];
      const remainingFuncs = [...funcs].slice(1);
    
      return remainingFuncs.reduce(
        (acc, item) => item.call(item, acc),
        entry(...arg)
      );
    };
    const add = (x: number, y: number) => x + y;
    const inc = (x: number) => add(1, x);
    const convertToString = (x: NonNullable<number>) => x.toString();
    
    function logResult(result: any, expected: any) {
      console.log(
        `The values expected is ${expected}, the value received is ${result}. Is it correct?  ${expected ===
          result}`
      );
    }
    
    namespace OneFunction {
      const piped = pipe(add);
      const result = piped(1, 2);
      const expected = 3;
      logResult(result, expected);
    }
    
    namespace TwoFunctions {
      const piped = pipe(add, inc);
      const result = piped(1, 2);
      const expected = 4;
      logResult(result, expected);
    }
    
    namespace ThreeFunctions {
      const piped = pipe(add, inc, convertToString);
      const result = piped(1, 2);
      const expected = "4";
      logResult(result, expected);
    }
    
    namespace CompilationError {
      const piped = pipe(add, inc, add, convertToString); // CompilationError
      const result = piped(1, 2);
      const expected = "4";
      logResult(result, expected);
    }
    

    【讨论】:

      猜你喜欢
      • 2019-02-03
      • 1970-01-01
      • 1970-01-01
      • 2019-05-26
      • 2019-07-17
      • 2013-10-07
      • 2020-07-10
      • 2013-01-17
      • 2019-12-18
      相关资源
      最近更新 更多