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

982 Views Asked by At

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.

2

There are 2 best solutions below

1
kschaer On BEST ANSWER

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">


1
captain-yossarian from Ukraine On

This is not direct answer to the question. Just a flowjs example:

// @flow

type TypeConstructor = <V>() => {updated:V};

type Obj = {
  a: number,
  b: string
}

type Result = $ObjMap<Obj, TypeConstructor>

const ok:Result = {
  a: {updated:42},
  b: {updated:42}
}; // ok

const error:Result = {
  a: 42,
  b: 42
}; // error

As you might have noticed, TypeConstructor is like callback.

It retrieves a type and converts it to another type. Each obj key/value pair is passed to TypeConstructor. It is impossible to do in TypeScript because TypeScript requires explicit generic for TypeConstructor.