Home > Blockchain >  How do I make a type-level list of varying Generics in TypeScript?
How do I make a type-level list of varying Generics in TypeScript?

Time:10-20

Given this type:

type AThingWithGenerics<A, B, C> = {
  name: string;
  someFunction: (id: number) => A;
  anotherFunction: () => B;
  loveSomeGenerics: (s: string) => { foo: C };
};

Where A, B, and C are objects that vary across values. For example:

const aThing: AThingWithGenerics<{ foo: string }, { foo: number, bar: string }, { bar: string }> = {
  name: "a_thing",
  someFunction: (id: number) => { return { foo: "bar" }; },
  anotherFunction: () => { return { foo: 3, bar: "foo" } },
  loveSomeGenerics: (s: string) => { return { foo: { bar: "In this case C is an object containing a string"   s } }; },
}

const anotherThing: AThingWithGenerics<{ bar: number }, { aString: string }, { bar: "test" }> = {
  name: "another_thing",
  someFunction: (id: number) => { return { bar: id   1 }; },
  anotherFunction: () => { return { aString: "test" } },
  loveSomeGenerics: (s: string) => { return { foo: { bar: "test" } } },
}

It's possible to create a tuple of specific AThingWithGenerics:

type ASpecificPileOfThings = [typeof aThing, typeof anotherThing];

It's also possible to make a list of AThingWithGenerics as long as A, B, and C do not vary:

type APileOfTheSameThing<A, B, C> = [AThingWithGenerics<A, B, C>];

But I am trying to add a list of related AThingWithGenerics to AThingWithGenerics, so aThing might include [anotherThing] as its related. The problem is A, B, and C in related will vary.

So how do I create a type that contains varying generics? This is what I have so far:

type AThingWithGenericsII<A, B, C> = {
  name: string;
  someFunction: (id: number) => A;
  anotherFunction: () => B;
  loveSomeGenerics: (s: string) => { foo: C };
  related: Array<AThingWithGenericsII<A,B,C>>
}

const anotherThingWithRelated: AThingWithGenericsII<{ bar: number }, { aString: string }, { bar: "test" }> = {
  name: "another_thing",
  someFunction: (id: number) => { return { bar: id   1 } },
  anotherFunction: () => { return { aString: "test" } },
  loveSomeGenerics: (s: string) => { return { foo: { bar: "test" } } },
  related: []
}

const aThingWithRelated: AThingWithGenericsII<{foo: string, bar: string }, { property: string }, { nestedFoo: number }> = {
  name: "a_thing_with_related",
  someFunction: (id: number) => { return { foo: "This is a string", bar: "" } },
  anotherFunction: () => { return { property: "bar" } },
  loveSomeGenerics: (s: string) => { return { foo: { nestedFoo: 3.1 } } },
  related: [anotherThingWithRelated]
}

This does not typecheck, since A, B, and Cs types do not unify. How can AThingWithGenericsII be changed so aThingWithRelated type checks?

TypeScript playground link. I'm sure this question about recursive Generics is related, but I couldn't get it to work for my case.

CodePudding user response:

Try using the first interface AThingWithGenerics you have defined but with this added in, you do not need the others :

type AThingWithGenerics<A = any, B = any, C = any> = {
  name: string;
  someFunction: (id: number) => A;
  anotherFunction: () => B;
  loveSomeGenerics: (s: string) => { foo: C };

  // some things might not have relatives
  related?: Array<AThingWithGenerics>
};

Using type any with the generic definitions will make typescript infer the types of generics used in a specific instance. We do this to allow the related property to use any generics that will ultimately be inferred by typescript.

Just to add the sentiment behind the interface, I figured not all "things" will be related so that is why it is possibly undefined, which was the initial ts error found on the link you provided.

Playgound

  • Related