Одно важное замечание: 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>)
)
)
)
)
);
особенно потому, что в этом конкретном определении используются перегрузки , которые могут играть не везде хорошо.
В любом случае, надеюсь, это поможет; удачи!
Детская площадка ссылка на код