TypeScript中的高阶类型函数?

zzlelutf  于 2023-05-08  发布在  TypeScript
关注(0)|答案(4)|浏览(174)

请考虑下面的伪代码,它试图定义一个高阶类型函数,该函数具有函数类型的参数M<?>

type HigherOrderTypeFn<T, M<?>> = T extends (...)
  ? M<T>
  : never;

M<?>在语法上是不正确的TypeScript,但将类型签名声明为HigherOrderTypeFn<T, M>会在第二行产生错误Type 'M' is not generic. ts(2315)
我假设这样一种类型目前在TS中是无法表示的,对吗?

qvtsj1bj

qvtsj1bj1#

你是对的,它目前无法在TypeScript中表示。有一个长期开放的GitHub功能请求,microsoft/TypeScript#1213,它可能应该被命名为“支持更高类型”,但目前的标题是“允许类在其他参数类中参数化”。
在讨论中有一些关于如何在当前语言中模拟这种高级类型的想法(具体示例请参阅此评论),但在我看来,它们可能不属于生产代码。如果您有一些特定的结构要实现,也许可以建议一些合适的东西。
但在任何情况下,如果你想增加这种情况发生的可能性(不幸的是,可能可以忽略不计),你可能想去那个问题,并给予它一个👍和/或描述你的用例,如果你认为它与已经存在的情况相比特别引人注目。!

rdrgkggo

rdrgkggo2#

对于你和其他正在寻找解决方法的人来说,你可以尝试一个基于占位符的简单想法(请参阅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)
798qvoo8

798qvoo83#

对于遇到这个问题的人来说,在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 & { [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> {}
}

其可以如下使用。下面的代码片段定义了一个SetFactory,在创建工厂时,您可以在其中指定所需的set类型,例如typeof FooSettypeof BarSettypeof FooSetFooSet的构造函数,类似于更高级的kinded类型,构造函数类型接受任何T并返回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();

简单解释一下它是如何工作的(以FooSetnumber为例):

  • 要理解的主要类型是Hkt.Output<Const["hkt"], T>。替换了我们的示例类型后,它变成了Hkt.Output<(typeof FooSet)["hkt"], number>。现在的魔术是将其转换为FooSet<number>
  • 首先我们解析(typeof FooSet)["hkt"]FooSetHkt。这里有很多神奇的地方,通过将有关如何创建FooSet的信息存储在FooSet的静态hkt属性中。您需要为每个支持的类执行此操作。
  • 现在我们有Hkt.Output<FooSetHkt, number>。解析Hkt.Output类型别名,我们得到(FooSetHkt & { [Hkt.input]: number })[typeof Hkt.output]。唯一的符号Hkt.input/Hkt.output有助于创建唯一的属性,但我们也可以使用唯一的字符串常量。
  • 现在我们需要访问FooSetHktHkt.output属性。这对于每个类都是不同的,并且包含了如何用类型参数构造具体类型的细节。FooSetHkt将output属性定义为FooSet<Hkt.Input<this>>类型。
  • 最后,Hkt.Input<this>只访问FooSetHktHkt.input属性。它将解析为unknown,但通过使用交集FooSetHkt & { [Hkt.input]: number },我们可以将Hkt.input属性更改为number。因此,如果我们达到了目标,Hkt.Input<this>将解析为numberFooSet<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>;
xqk2d5yq

xqk2d5yq4#

fp-ts中有一个HKT(利用模块增强)的实现。
高Kinded类型as documented here by its author的解决方法是:

export interface HKT<URI, A> {
  readonly _URI: URI;
  readonly _A: A;
}

可以这样使用:

export interface Foldable<F> {
  readonly URI: F;
  reduce: <A, B>(fa: HKT<F, A>, b: B, f: (b: B, a: A) => B) => B;
}

看看这个问题:higher kinded type in typescript from fp-ts and URI
也许这能提供一些线索
干杯

相关问题