Как типизировать 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;
}
Вариант с перегрузкой функции слишком многословный для такой маленькой рекурсивной функции.
Можно использовать рекурсивный тип.
Вот мой вариант, он тоже многословный, но не настолько, как с перегрузкой функции. Ну и мне кажется, что его можно отрефакторить, потому что вообще не хочется усложнять простую функцию таким количестовом типизации, хочется чего-то лаконичного.
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>;
};