Search code examples
typescriptflowtypetypescript-genericshigher-kinded-typeshigher-order-types

Pass parametric type as a type argument to another type in TypeScript or Flow


For the purposes of demonstration, consider the following types:

type ToEnglish<A extends string> = 
  A extends "1" ? "one" :
  A extends "2" ? "two" :
  A extends "3" ? "three" :
  "etc";

type Convert<A extends string> =
  A extends `${infer C}${infer Tail}` ? 
    `${ToEnglish<C>}-${Convert<Tail>}` : "";

So for example, Convert<"12"> results in "one-two-".

Now I would like to make it more generic and accept a "translator" such as ToEnglish above as an argument:

type Convert2<A extends string, Translator> =
  A extends `${infer C}${infer Tail}` ? 
    `${Translator<C>}-${Convert<Tail>}` : "";

This won't work: Type 'Translator' is not generic. ts(2315)

If I try to write:

type Convert3<A extends string, Translator<_>> = 

I get: ',' expected. ts(1005) at the <_>.

Q: Is there a way to pass a parametric (generic) type as an argument (parameter) to another type somehow in TypeScript, Flow, or another JavaScript superset?
Similar to higher-order functions, but for types.


Solution

  • I'd suggest a solution that bypasses having to pass the generic type as a type parameter directly. For instance, by using what is essentially a record of translators, you can instead pass the name of a given translator and access the type:

    Playground

    type ToEnglish<A extends string> = 
      A extends "1" ? "one" :
      A extends "2" ? "two" :
      A extends "3" ? "three" :
      "etc";
    
    type ToSpanish<A extends string> = 
      A extends "1" ? "uno" :
      A extends "2" ? "dos" :
      A extends "3" ? "tres" :
      "etc";
    
    
    type TranslatorMap<A extends string> = {
        English: ToEnglish<A>;
        Spanish: ToSpanish<A>;
    }
    
    type ConvertGeneric<A extends string, Translator extends keyof TranslatorMap<A>> =
      A extends `${infer C}${infer Tail}` ? 
        `${TranslatorMap<C>[Translator]}-${ConvertGeneric<Tail, Translator>}` : "";
    
    type EnglishTest = ConvertGeneric<"12", "English">
    type SpanishTest = ConvertGeneric<"12", "Spanish">