Как типизировать js функцию для каррирования?

Вот функция каррирования, которую надо типизировать:

Функция принимает число Если число 0, то возвращается число. Если число не 0 (не falsy), то возвращается функция, которая принимает число и возвращает число или функцию.

const sum = (a = 0) => {
  if (!a) return 0;

  return function (b: number) {
    if (b === undefined) return a;
    return sum(a + b);
  };
};

Вот как функция используется:

// sum() // 0
// sum(1)() // 1
// sum(1)(4)() // 5
// sum(5)(2)(2)() // 9


Ответы (2 шт):

Автор решения: Швеев Алексей

Для упрощения уберём лишние runtime вычисления:

function sum(a: number) {
  return function (b?: number) {
    if (b === undefined) return a;
    return sum(a + b);
  };
};

Тогда можно добиться типизации с помощью перегрузки сигнатуры функции:

type RType = {
  (b: number): RType;
  (): number;
}

function sum(a: number) {
  function internalSum(b: number): RType
  function internalSum(): number;
  function internalSum(b?: number): number | RType {
    if (b === undefined) return a;
    return sum(a + b);
  }

  return internalSum;
}

// type of sum(1)(2) == {(b: number): RType; (): number}
// type of sum(1)(2)() == number

Где возвращаемый тип зависит от наличия аргумента.

В данной реализации RType по сути описывает возвращаемый тип перегруженной функции internalSum.

Выведенная сигнатура функции sum:

function sum(a: number): {
    (b: number): RType;
    (): number;
} {...}

Возвращаясь к более сложной версии:

function sum(a?: number) {
  if (a === 0 || a === undefined) {
    return 0;
  }
  return function (b?: number) {
    if (b === undefined) return a;
    return sum(a + b);
  }
}

Теми же методами получаем следующее:

type RType = {
  (b: number): RType;
  (): number;
}

function sum(a: 0): number
function sum(): number
function sum(a: number): {
  (b: number): RType;
  (): number;
}
function sum(a?: number): {
    (b: number): RType;
    (): number;
} | number {
  if (a === 0 || a === undefined) {
    return 0;
  }
  let a_ = a;
  function internalSum(b: number): RType
  function internalSum(): number;
  function internalSum(b?: number): number | RType {
    if (b === undefined) return a_;
    return sum(a_ + b);
  }

  return internalSum;
}
→ Ссылка
Автор решения: Cheshire's Smile

Вариант с перегрузкой функции слишком многословный для такой маленькой рекурсивной функции.

Можно использовать рекурсивный тип.

Вот мой вариант, он тоже многословный, но не настолько, как с перегрузкой функции. Ну и мне кажется, что его можно отрефакторить, потому что вообще не хочется усложнять простую функцию таким количестовом типизации, хочется чего-то лаконичного.

type IsFalsy<A> = A extends 0 | undefined | null | false ? false : true;
type sumType<A> = IsFalsy<A> extends false
  ? number
  : (b?: number) => sumType<number | undefined>;

const sum = <A extends number>(a: A = 0 as A): sumType<A> => {
  if (!a) return 0 as sumType<A>;

  return function (b?: number) {
    if (b === undefined) return a as unknown as sumType<number>;
    return sum(a + b);
  } as sumType<A>;
};

→ Ссылка