rekursion - TypeScript rekursiv typ med särskild djup


TypeScript tillåter dig att komponera rekursiva typer men ger ingen insyn i hur koden förändras på lägre nivåer (dvs. djup). Koden nedan, till exempel, skulle ha samma typ av signatur på alla nivåer, och vi skulle behöva manuellt kontrollera på varje nivå att det finns en sub-egenskap.

type Recurse = { foo: string; sub?: Recurse }

function recurse(depth: number): Recurse {
  if (depth === 0) return { foo: 'hey' }
  return {
    foo: 'hey',
    sub: recurse(depth - 1),
  }
}
const qux = recurse(5)

Det jag söker är en typdeklaration som skulle ge oss konkreta bevis på vad funktionen returnerade på ett visst djup.

const qux0: { foo: string } = recurse(0)
const qux1: { foo: string, sub: { foo: string } } = recurse(1)
const qux2: { foo: string, sub: {  foo: string, sub: { foo: string }} } = recurse(2)

På det här sättet skulle vi slippa kontrollera på varje nivå för sub-egenskapen eftersom typdeklarationen redan innehåller den informationen.

Jag har en känsla av att detta kan vara möjligt att uppnå med villkorliga typer, men jag har inga konkreta bevis.

Har du någon aning om hur jag skulle kunna uppnå det?

Svar #1

Tills TS tillåter grundläggande aritmetiska operationer för number-typer, kan du använda en dekrementeringsräknare som Decr för alla rekursiva typer med särskild djup:

type Decr = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // add to a reasonable amount

type Recurse<N extends number> = N extends 0 ? 
  { foo: string } : 
  { foo: string; sub: Recurse<Decr[N]> }

function recurse<N extends number>(depth: N): Recurse<N> {
  if (depth === 0) return { foo: 'hey' } as Recurse<N>
  return {
    foo: 'hey',
    sub: recurse(depth - 1),
  } as Recurse<N>
}
type Decr9 = Decr[9] // 8

// compiles now
const qux = recurse(5)
const qux0: { foo: string } = recurse(0)
const qux1: { foo: string, sub: { foo: string } } = recurse(1)
const qux2: { foo: string, sub: { foo: string, sub: { foo: string } } } = recurse(2)

Exempel

Svar #2

Jag tror att du förmodligen kan få något att fungera med rekursiva villkorliga typer, men jag skulle rekommendera att inte använda en sådan lösning. Om någon till exempel skulle anropa recurse(5000) skulle returtypen bli enorm.

Du kan enkelt bygga en lösning baserad på en kartläggningstyp, där du lägger till typen av ett barn upp till ett visst djup:

type Recurse<T extends number =  number> = {
  foo: string;
  sub: RecurseChildren extends Record<T, infer C> ? C : Recurse
}

interface RecurseChildren {
  0: undefined;
  1: Recurse<0>
  2: Recurse<1>
  3: Recurse<2>
  4: Recurse<3>
  5: Recurse<4>
  6: Recurse<5>
  7: Recurse<6>
  8: Recurse<7>
  9: Recurse<8>
  10: Recurse<9>
}


function recurse<T extends number>(depth: T): Recurse<T> {
  if (depth === 0) return { foo: 'hey', sub: undefined as any }
  return {
    foo: 'hey',
    sub: recurse(depth - 1) as any,
  }
}
const qux = recurse(5)

qux.sub.sub.sub.sub.sub.sub // last one is undefined 

Lekplatslänk

Relaterade artiklar