Компоновка безопасной функции TypeScript - PullRequest
3 голосов
/ 03 марта 2020

Этот код взят из потрясающей статьи Представляем Рекурсивные Pipe и Compose Типы .

Работает на 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);

Вопросы

  • Почему де код не компилируется в текущей версии 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'.

1 Ответ

0 голосов
/ 08 марта 2020

Не удалось ответить на вопрос ниже.

Почему код не компилируется в текущей версии 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);
}
...