【问题标题】:Higher-order type functions in TypeScript?TypeScript 中的高阶类型函数?
【发布时间】:2020-05-17 07:58:51
【问题描述】:

请考虑以下伪代码尝试使用函数类型参数M<?> 定义高阶类型函数:

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

M&lt;?&gt; 在语法上是不正确的 TypeScript,但将类型签名声明为 HigherOrderTypeFn&lt;T, M&gt; 会在第二行产生错误 Type 'M' is not generic. ts(2315)

假设这种类型目前在 TS 中无法表示,我是否正确?

【问题讨论】:

    标签: typescript generics nested-generics open-generics


    【解决方案1】:

    你是对的,它目前无法在 TypeScript 中表示。有一个长期开放的 GitHub 功能请求,microsoft/TypeScript#1213,它的标题可能应该是“支持更高种类的类型”,但目前的标题是“允许类在其他参数类中参数化”。

    讨论中有一些关于如何在当前语言中模拟这种更高种类的类型的想法(具体示例请参见this comment),但在我看来,它们可能不属于生产代码。如果您有一些要实施的特定结构,也许可以建议一些适当的东西。

    但无论如何,如果您想增加这种情况发生的机会(可能可以忽略不计,不幸的是),您可能想要解决该问题并给它一个 ? 和/或描述您的用例,如果您认为它是与已经存在的相比,特别引人注目。好的,希望有帮助;祝你好运!

    【讨论】:

    • 感谢@jcalz 清除此问题!这是我的一个实验(试图干掉我的打字),但我同意我最好在生产中以更简洁(尽管更冗余)的方式实现它。很高兴看到这种结构在当前的 TS 语义下是可行的。
    • 这个讨论让我觉得未来的编程语言会很像Haskell...
    【解决方案2】:

    对于您和其他寻找解决方法的人,您可以尝试基于占位符的简单想法(请参阅 jcalz 提到的讨论中的this comment):

    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 & { [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> {}
      }
      

      可以如下使用。下面的sn-p定义了一个SetFactory,在这里你可以在创建工厂的时候指定想要的set类型,例如typeof FooSettypeof BarSettypeof FooSetFooSet 的构造函数,类似于更高种类的类型,构造函数类型接受任何 T 并返回 FooSet&lt;T&gt;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&lt;Const["hkt"], T&gt;。替换我们的示例类型后,它变为Hkt.Output&lt;(typeof FooSet)["hkt"], number&gt;。魔术现在涉及将其变成FooSet&lt;number&gt;
      • 首先我们将(typeof FooSet)["hkt"] 解析为FooSetHkt。通过将有关如何创建FooSet 的信息存储在FooSet 的静态hkt 属性中,很多魔法都在这里。您需要为每个受支持的类执行此操作。
      • 现在我们有Hkt.Output&lt;FooSetHkt, number&gt;。解析Hkt.Output 类型别名,我们得到(FooSetHkt &amp; { [Hkt.input]: number })[typeof Hkt.output]。唯一符号 Hkt.input / Hkt.output 有助于创建唯一属性,但我们也可以使用唯一字符串常量。
      • 现在我们需要访问FooSetHktHkt.output 属性。这对于每个类都不同,并且包含有关如何使用类型参数构造具体类型的详细信息。 FooSetHkt 将输出属性定义为 FooSet&lt;Hkt.Input&lt;this&gt;&gt; 类型。
      • 最后,Hkt.Input&lt;this&gt; 只是访问了FooSetHktHkt.input 属性。它将解析为unknown,但通过使用交集FooSetHkt &amp; { [Hkt.input]: number },我们可以将Hkt.input 属性更改为number。因此,如果我们达到了目标,Hkt.Input&lt;this&gt; 将解析为 numberFooSet&lt;Hkt.Input&lt;this&gt;&gt; 将解析为 FooSet&lt;number&gt;

      对于问题中的示例,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>;
      

      【讨论】:

        猜你喜欢
        • 2012-10-25
        • 1970-01-01
        • 1970-01-01
        • 2020-02-16
        • 1970-01-01
        • 2019-07-25
        • 1970-01-01
        相关资源
        最近更新 更多