Как набрать автовыбор в TypeScript - PullRequest
1 голос
/ 18 февраля 2020

Я пытаюсь написать типы для следующей функции:

const curry = (
  f, arr = []
) => (...args) => (
  a => a.length >= f.length ?
    f(...a) :
    curry(f, a)
)([...arr, ...args]);

Я нашел эту классную статью , которая создала тип для curry, например:

type Head<T extends any[]> =
  T extends [any, ...any[]]
  ? T[0]
  : never;

type Tail<T extends any[]> =
  ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any)
  ? TT
  : [];

type HasTail<T extends any[]> =
  T extends ([] | [any])
  ? false
  : true;

type Curry<P extends any[], R> = 
  (arg: Head<P>) => HasTail<P> extends true
  ? Curry<Tail<P>, R>
  : R;

Моя проблема в том, что подпись этого типа Curry не соответствует моей функции curry (или мои навыки в TypeScript плохие, и это так?). Кроме того, я не могу понять, как написать реализацию для curry, которая соответствует типу Curry.

Как можно реализовать типы для моего curry? И как будет выглядеть реализация, использующая тип Curry?

1 Ответ

2 голосов
/ 18 февраля 2020

Одно важное замечание: Function.length странно; это будет хорошо работать только для функций без аргументов по умолчанию или остальных аргументов. Например, в зависимости от того, на какую версию JS нацелен ваш компилятор TS, вы можете получить разные ответы для следующего кода:

console.log(((arg = 1) => { }).length); // 0? 1?

Так что имейте это в виду, когда используете все, что зависит от отражения во время выполнения длины аргумента функции.


Ваша функция curry() может быть назначена определению типа Curry, но не наоборот. Определение типа Curry ожидает ровно один аргумент при каждом вызове, если базовой функции все еще нужны аргументы, тогда как ваш curry примет любое количество аргументов при вызове. Это означает, что мы можем дать curry() тип Curry, но компилятор ограничит количество аргументов, которые вы вызываете. Это может выглядеть так:

function curry<P extends any[], R>(f: (...args: P) => R): Curry<Required<P>, R> {
  const _curry = (
    f: (...args: any) => any, arr: any[] = []
  ) => (...args: any) => (
    a => a.length >= f.length ?
      f(...a) :
      _curry(f, a)
  )([...arr, ...args]);
  return _curry(f);
}

Здесь мы практически отказываемся от того, чтобы компилятор делал какую-либо реальную проверку типов внутри реализации; все внутри более или менее типа any. Вы могли бы быть в состоянии улучшить безопасность типов, но не намного; компилятор не сможет проверить манипуляции, связанные с добавлением в конец типа кортежа, и вы в конечном итоге будете использовать везде утверждения типа .

Бит Required<P> может не быть необходимым; это зависит только от того, что вы хотите видеть для функций с необязательными аргументами. В общем, я бы очень осторожно использовал это для любых функций объединяющих типов или чьи списки аргументов могут быть разной длины, и т. Д. c.

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

function test(x: string, y: number, z: boolean) {
  return z ? x : y;
}

const t0 = curry(test);
const t1 = t0("abc");
const t2 = t1(123);
const t3 = t2(true); 
console.log(t3); // abc
const t4 = t2(false);
console.log(t4); // 123

Хорошо выглядит.


Что касается того, как вы будете печатать TS для полной версии curry(), возможно, с несколькими аргументами, то для этого требуется конкатенация кортежей на уровне типов, которая в настоящее время не является прямой. поддерживается ( см. Microsoft / TypeScript # 5453 ). Вы можете написать что-то, что работает с рекурсивными условными типами, но поскольку рекурсивные типы также не поддерживаются напрямую ( см. Microsoft / TypeScript # 26980 ), я бы не рекомендовал их для производственных систем.

Или вы можете выбрать максимальную длину для поддержки, например, трех аргументов за раз, и написать версию Curry, которая работает для этого, но я не знаю, действительно ли это того стоит:

type Curry3<P extends any[], R> = P extends [] ? R : (
  ((a0: Head<P>) => Curry3<Tail<P>, R>) & (
    P extends [any] ? unknown : (
      ((a0: Head<P>, a1: Head<Tail<P>>) => Curry3<Tail<Tail<P>>, R>) & (
        P extends [any, any] ? unknown : (
          ((a0: Head<P>, a1: Head<Tail<P>>, a2: Head<Tail<Tail<P>>>) =>
            Curry3<Tail<Tail<Tail<P>>>, R>)
        )
      )
    )
  )
);

особенно потому, что в этом конкретном определении используются перегрузки , которые могут играть не везде хорошо.

В любом случае, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

...