Я собираюсь использовать следующее объявление:
declare const mapP: <T, R>(xf: (value: T) => Promise<R> | R) => (
data: T[],
) => Promise<R[]>;
, которое совпадает с вашей версией, но не беспокоится о реализации. В любом случае, вы столкнулись с проблемой, из-за которой следующее не может определить тип x
в обратном вызове:
const res = mapP(x => x.name)(data); // error!
// x is unknown ----> ~
Это не полностью , поэтому не следует ожидать, что компилятор может сделайте вывод, что x
должен иметь тип Foo
, потому что data
имеет тип Foo[]
. Существует такая вещь, как контекстная типизация , при которой компилятор выводит типы «назад во времени», наблюдая за тем, как что-то используется, и выясняя, какой тип должен был быть объявлен как чтобы это работало. К сожалению, это слишком сложно для компилятора, чтобы сделать это в обратном направлении через несколько вызовов функций. Ну хорошо.
Большая привлекательность карри, на мой взгляд, заключается в возможности частично применить функцию и затем использовать эту частично примененную функцию позже , например так:
const f = mapP(x => x.name); // error!
// x is unknown --> ~
// later
const res2 = f(data);
В этом случае было бы неправдоподобно ожидать, что компилятор может знать что-нибудь полезное о x
, особенно в свете других возможных вызовов, таких как:
const res3 = f([{ name: false }]);
где x
должно быть {name: boolean}
вместо Foo
. Если ваше намерение в x => x.name
состоит в том, чтобы x
было Foo
, вам нужно сообщить это намерение компилятору с помощью аннотации типа:
const res4 = mapP((x: Foo) => x.name)(data); // okay Promise<string[]>
Это решение, которое я ' буду рекомендовать ваш вопрос, как указано; вам не требуется, чтобы разработчик вручную указывал T
и R
при вызове mapP()
. Вместо этого вы аннотируете параметр обратного вызова, чтобы компилятор мог определить T
и R
сам, что он и делает.
Обратите внимание, что вы можете даже стать более модным и общаться " Я хотел бы, чтобы обратный вызов применялся к что угодно со свойством name
и возвращал значение этого типа ", используя обратный вызов generi c:
const g = mapP(<T>(x: { name: T }) => x.name);
const res5 = g(data); // Promise<string[]>;
const res6 = g([{ name: false }]); // Promise<boolean[]>;
И здесь компилятор может использовать некоторые из своих выводов более высокого порядка , представленных в TS3.4, чтобы увидеть, что g()
сама является обобщенной c функцией.
Итак, резервное копирование: если ваш вариант использования действительно заключается в немедленном использовании частично примененной функции, вызывая ее без ссылки на нее, вы, вероятно, должны делать это без каррирования. Подход «лучший из обоих миров» может быть перегруженной гибридной функцией, которая одновременно и карри, и не карри:
function mapQ<T, R>(xf: (value: T) => Promise<R> | R, data: T[]): Promise<R[]>;
function mapQ<T, R>(xf: (value: T) => Promise<R> | R): (data: T[]) => Promise<R[]>;
function mapQ<T, R>(
xf: (value: T) => Promise<R> | R,
data?: T[]
): ((data: T[]) => Promise<R[]>) | Promise<R[]> {
return data ? mapQ(xf)(data) : mapQ(xf);
}
Тогда вы можете использовать ее карри в том и только в том случае, если ваше намерение - использовать функцию позже :
const res7 = mapQ(x => x.name, data); // okay Promise<string[]>
const h = mapQ((x: Foo) => x.name);
const res8 = h(data); // okay Promise<string[]>
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код