Композиция функций в Typescript без перегрузок - PullRequest
0 голосов
/ 16 января 2019

Можно ли определить тип Typescript для композиции функции (см. flow или pipe ) для любого количества аргументов (функций для создания) без перезаписи , но с возможностью подсказки типов?

Без вывода типа есть изумительный ответ в моем предыдущем вопросе .

Увы, это решение только проверяет цепочку и сообщает об ошибках при явном определении типов:

const badChain = flow(
  (x: number)=>"string",
  (y: string)=>false,
  (z: number)=>"oops"
); // error, boolean not assignable to number

Но все аргументы

flow(
  (x: number)=>"string",
  (y: string)=>false,
  z => {/*z is any, but should be inferred as boolean*/}
);

Этот вывод работает в типах lodash и ramda, но его определение использует длительно не поддерживаемые перегрузки, как указано в моем предыдущем вопросе.

Есть ли способ избежать перезаписи и не потерять вывод типа?

Ответы [ 3 ]

0 голосов
/ 15 апреля 2019

Может быть не совсем очевидно, почему перегрузки все еще требуются. Я играл с различными реализациями и дошел до сути проблемы, которой я верю, по крайней мере, с учетом одного подхода, который решается вокруг перечисления всех цепочек снизу вверх (что немного лучше, чем перегрузки, потому что вы можете ссылаться на более низкие уровни), и вы должен все еще получить вывод типа.

const a: [(_: string) => number, (_: number) => boolean] | [(_: string) => boolean] = [x => x.length, y => true]

Перегрузки нужны только для определения кортежа по его длине. TS может управлять этим с перегрузками (выбрать сигнатуру на основе количества аргументов), но не может сделать это с помощью простых кортежей без сигнатур функций. Вот почему y не выводится во фрагменте кода, и поэтому попытки сделать решение более компактным не могут быть выполнены с текущим состоянием без перегрузок.

Итак, одобренный ответ кажется лучшим решением на данный момент!

0 голосов
/ 06 мая 2019

Этому сейчас четыре года, но мне удалось заставить печатную версию работать без перегрузок:

Необходима пара неприятных вещей:

Есть две причины, по которым нам нужны списки чисел:

  • Учитывая определенный индекс, мы должны иметь возможность получить предыдущий индекс
  • Нам нужно иметь возможность преобразовать строковые индексы кортежей в числовые индексы
type SNumbers = [
   "0",  "1",  "2",  "3",  "4",  "5",  "6",  "7",  "8",  "9",  "10", "11", "12", "13", "14", "15",
   "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31",
   "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47",
   "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63"];

type Numbers = [
   0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
   16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
   32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
   48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63];

// Util to prepend a value to a Tuple from: https://stackoverflow.com/a/54607819/5308589
type PrependTuple<A, T extends Array<any>> =
  (((a: A, ...b: T) => void) extends (...a: infer I) => void ? I : [])

// Get the previous number (for indexing)    (2=>1, 1=>0, 0=>never)
type PrevN<T extends number> = PrependTuple<never, Numbers>[T];

// Convert a string index to a number
type S_N<S extends SNumbers[number]> = {
   [K in SNumbers[number]]: Numbers[K]
}[S]

Пара помощников:

Трубный / составной акт на одинарные функции

// Only unary functions wanted 
type Unary = (i: any) => any;

// Get the (single) argument of a given unary function
type ParameterUnary<F extends Unary> = Parameters<F>["0"]

// ReturnType is a builtin

Основные типы:

UnariesToPiped / UnariesToComposed берет кортеж унарных функций и пытается сопоставить его кортежу, содержащему правильные типы функций

Затем Pipe / Compose просто использует сопоставленный кортеж в качестве аргумента и извлекает первый тип параметра и последний возвращаемый тип.

type UnariesToPiped<F extends Unary[]> = {
   [K in keyof F]:
   K extends SNumbers[number] 
      ? K extends "0"
         ? F[K]
         : (i: ReturnType<F[PrevN<S_N<K>>]>) => ReturnType<F[S_N<K>]>
      : F[K]
}

type Pipe = <F extends Unary[]>(...funcs: UnariesToPiped<F>) => (i: ParameterUnary<F[0]>) => ReturnType<F[PrevN<F["length"]>]>

type UnariesToComposed<F extends Unary[]> = {
   [K in keyof F]:
   K extends SNumbers[number] 
      ? K extends "0"
         ? F[K]
         : (i: ParameterUnary<F[S_N<K>]>) => ParameterUnary<F[PrevN<S_N<K>>]>
      : F[K]
}

type Compose = <F extends Unary[]>(...funcs: UnariesToComposed<F>) => (i: ParameterUnary<F[PrevN<F["length"]>]>) => ReturnType<F[0]>

Для примеров использования я опубликовал это на Github и NPM

0 голосов
/ 16 января 2019

Нет способа убрать все перегрузки. То, как параметры типа R* зависят друг от друга, в настоящее время не выражается в системе типов.

Одним из улучшений, которое мы можем сделать, является устранение необходимости в перегрузках, добавляя дополнительные параметры в первую функцию (те, которые добавляют параметры типа A*). Это можно сделать в 3.0 с использованием кортежей в параметрах отдыха

interface LoDashStatic {

    flow<A extends any[], R1, R2>(f1: (...a: A) => R1, f2: (a: R1) => R2): (...a: A) => R2;

    flow<A extends any[], R1, R2, R3>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (...a: A) => R3;

    flow<A extends any[], R1, R2, R3, R4>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (...a: A) => R4;

    flow<A extends any[], R1, R2, R3, R4, R5>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (...a: A) => R5;

    flow<A extends any[], R1, R2, R3, R4, R5, R6>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (...a: A) => R6;

    flow<A extends any[], R1, R2, R3, R4, R5, R6, R7>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (...a: A) => R7;

}

declare const _: LoDashStatic;

let f = _.flow((n: number, s: string) => n + s, o => o.toUpperCase()); // f: (n: number, s: string) => string
...