#typescript #generics #typescript-generics
Вопрос:
Пожалуйста, рассмотрите следующий псевдокод, пытающийся определить функцию типа более высокого порядка с параметром, типизированным функцией M<?>
:
type HigherOrderTypeFn<T, M<?>> = T extends (...)
? M<T>
: never;
M<?>
синтаксически неверный текст, но объявление подписи типа как HigherOrderTypeFn<T, M>
приводит к ошибке Type 'M' is not generic. ts(2315)
во второй строке.
Правильно ли я предполагаю, что такой тип в настоящее время не представлен в TS?
Ответ №1:
Вы правы, в настоящее время это невозможно представить в машинописном виде. Существует давний запрос на функцию open GitHub, microsoft/TypeScript#1213, который, вероятно, должен называться что-то вроде «поддержка типов с более высоким уровнем сложности», но в настоящее время имеет заголовок «Разрешить классам быть параметрическими в других параметрических классах».
В обсуждении есть некоторые идеи о том, как имитировать такие типы более высокого уровня на текущем языке (см. Этот комментарий для конкретного примера), но, на мой взгляд, они, вероятно, не относятся к производственному коду. Если у вас есть какая-то конкретная структура, которую вы собираетесь реализовать, возможно, можно предложить что — то подходящее.
Но в любом случае, если вы хотите увеличить вероятность (вероятно, незначительно, к сожалению), что это когда-либо произойдет, вы можете обратиться к этой проблеме и описать ее 👍 и/или описать свой вариант использования, если вы считаете, что он особенно убедителен по сравнению с тем, что уже есть. Хорошо, надеюсь, это поможет; удачи!
Комментарии:
1. Спасибо @jcalz за прояснение этого вопроса! Это был эксперимент с моей стороны (попытка сократить набор текста), но я согласен, что мне будет лучше реализовать это более лаконичным (хотя и более избыточным) способом в производстве. Приятно видеть, однако, что такая конструкция выполнима с текущей семантикой TS.
2. Это обсуждение заставляет меня думать, что будущие языки программирования будут очень похожи на Хаскелл…
Ответ №2:
Для вас и других, кто ищет обходной путь, вы можете попробовать простую идею, основанную на заполнителях (см. Этот комментарий в обсуждении, упомянутом jcalz).:
type Placeholder = {'aUniqueKey': unknown};
type Replace<T, X, Y> = {
[k in keyof T]: T[k] extends X ? Y : T[k];
};
Итак, ваша функция будет выглядеть следующим образом:
type HigherOrderTypeFn<T, M> = T extends (...) ? Replace<M, Placeholder, T> : never;
и называться, например, так:
type M<U> = U[];
type X = HigherOrderTypeFn<number, M<Placeholder>> // is number[] (if ... is number)
Ответ №3:
Для людей, столкнувшихся с этим, на сервере TypeScript discord есть такой хороший пример:
export interface Hkt<I = unknown, O = unknown> {
[Hkt.isHkt]: never,
[Hkt.input]: I,
[Hkt.output]: O,
}
export declare namespace Hkt {
const isHkt: unique symbol
const input: unique symbol
const output: unique symbol
type Input<T extends Hkt<any, any>> =
T[typeof Hkt.input]
type Output<T extends Hkt<any, any>, I extends Input<T>> =
(T amp; { [input]: I })[typeof output]
interface Compose<O, A extends Hkt<any, O>, B extends Hkt<any, Input<A>>> extends Hkt<Input<B>, O>{
[output]: Output<A, Output<B, Input<this>>>,
}
interface Constant<T, I = unknown> extends Hkt<I, T> {}
}
Который может быть использован следующим образом. Приведенный ниже фрагмент определяет a SetFactory
, где вы указываете тип, желаемый тип набора при создании фабрики, например typeof FooSet
, или typeof BarSet
. typeof FooSet
является конструктором для a FooSet
и похож на более высокий тип, тип конструктора принимает любой T
и возвращает a FooSet<T>
. SetFactory
Содержит несколько методов , таких как createNumberSet
, который возвращает новый набор данного типа с параметрами типа, установленными на number
.
interface FooSetHkt extends Hkt<unknown, FooSet<any>> {
[Hkt.output]: FooSet<Hkt.Input<this>>
}
class FooSet<T> extends Set<T> {
foo() {}
static hkt: FooSetHkt;
}
interface BarSetHkt extends Hkt<unknown, BarSet<any>> {
[Hkt.output]: BarSet<Hkt.Input<this>>;
}
class BarSet<T> extends Set<T> {
bar() {}
static hkt: BarSetHkt;
}
class SetFactory<Cons extends {
new <T>(): Hkt.Output<Cons["hkt"], T>;
hkt: Hkt<unknown, Set<any>>;
}> {
constructor(private Ctr: Cons) {}
createNumberSet() { return new this.Ctr<number>(); }
createStringSet() { return new this.Ctr<string>(); }
}
// SetFactory<typeof FooSet>
const fooFactory = new SetFactory(FooSet);
// SetFactory<typeof BarSet>
const barFactory = new SetFactory(BarSet);
// FooSet<number>
fooFactory.createNumberSet();
// FooSet<string>
fooFactory.createStringSet();
// BarSet<number>
barFactory.createNumberSet();
// BarSet<string>
barFactory.createStringSet();
Краткое объяснение того, как это работает (с FooSet
примером и number
в качестве примера):
- Основной тип, который нужно понять, таков
Hkt.Output<Const["hkt"], T>
. С заменой наших типов примеров это становитсяHkt.Output<(typeof FooSet)["hkt"], number>
. Магия теперь включает в себя превращение этого вFooSet<number>
- Сначала мы решаем
(typeof FooSet)["hkt"]
FooSetHkt
это сделать . Большая часть магии кроется здесь в хранении информации о том, как создать aFooSet
в статическомhkt
свойствеFooSet
. Вам нужно сделать это для каждого поддерживаемого класса. - Теперь у нас есть
Hkt.Output<FooSetHkt, number>
. РазрешаяHkt.Output
псевдоним типа, мы получаем(FooSetHkt amp; { [Hkt.input]: number })[typeof Hkt.output]
. Уникальные символыHkt.input
/Hkt.output
справка для создания уникальных свойств, но мы могли бы также использовать уникальные строковые константы. - Теперь нам нужно получить доступ к
Hkt.output
собственностиFooSetHkt
. Это отличается для каждого класса и содержит подробные сведения о том, как создать конкретный тип с аргументом типа.FooSetHkt
определяет свойство вывода, которое должно иметь типFooSet<Hkt.Input<this>>
. - Наконец,
Hkt.Input<this>
просто присоединяется кHkt.input
свойствуFooSetHkt
. Это разрешило быunknown
, но , используя пересечениеFooSetHkt amp; { [Hkt.input]: number }
, мы можем изменитьHkt.input
свойство наnumber
. И поэтому, если мы достигли нашей цели,Hkt.Input<this>
решаемсяnumber
иFooSet<Hkt.Input<this>>
решаемсяFooSet<number>
.
Для примера из вопроса, Hkt.Output
по сути, это то, о чем просили, просто с измененными параметрами типа:
interface List<T> {}
interface ListHkt extends Hkt<unknown, List<any>> {
[Hkt.output]: List<Hkt.Input<this>>
}
type HigherOrderTypeFn<T, M extends Hkt> = Hkt.Output<M, T>;
// Gives you List<number>
type X = HigherOrderTypeFn<number, ListHkt>;