typescript - Definiera en array-typ med matchande par (som dominobrickor) i typescript


Jag försöker skapa en typalias i TypeScript till vilken alla värden som är arrayer av Par kan tilldelas och där varje par matchar det efterföljande paret som dominobrickor gör: Par<A,B> matchar Par<C,D> om och endast om B=C.

Exempelvärden:

const chain1: DominoChain = [ ["A","B"], ["B","C"], ["C","D"], ["D","E"] ] // OK
const chain2: DominoChain = [ [3, 4], [4, 2], [2, 1] ] // OK
const chain3: DominoChain = [ [3, null], [null, {}], [{}, undefined] ] // OK
const chain1: DominoChain = [ [1, 2] ] // OK
const chain1: DominoChain = [ ] // OK
const chain1: DominoChain = [ [1, 2], [3, 4] ] // Not OK because 2 !== 3. The compiler should raise an error

Här är vad jag har hittills, men det fungerar inte:

type Domino<A,B> = [A, B]

type DominoChain<A = any, B = any> = [Domino<A, B>] | [Domino<A, B>, ...DominoChain<B, any>]

Jag vet inte hur man korrekt definierar matchningsrestriktionen mellan två Domino-par och jag gör något fel med rekursionsdelen.

Svar #1

Det är inte möjligt att uttrycka en sådan begränsning i TypeScript-typsystemet utan att validera generiskt argument.

Det är dock möjligt att skapa ett typverktyg som kommer att validera din inmatning.

Tänk på det här exemplet:

type Last<T> = T extends [...infer _, infer LastElem] ? LastElem : never

type Head<T> = T extends [infer First, ...infer _] ? First : never

type IsChainable<Current, Next> =
  (Last<Current> extends Head<Next>
    ? (Head<Next> extends Last<Current>
      ? true
      : false)
    : false)

type DominoChain<List extends any[], Result extends any[] = []> =
  (List extends []
    ? Result
    : (List extends [infer First]
      ? (IsChainable<Last<Result>, First> extends true
        ? [...Result, First]
        : never)
      : (List extends [infer First, ...infer Rest]
        ? (Result extends []
          ? DominoChain<Rest, [...Result, First]>
          : (IsChainable<First, Head<Rest>> extends true
            ? DominoChain<Rest, [...Result, First]>
            : never))
        : never
      )
    )
  )

type Test1 = DominoChain<[["A", "B"], ["B", "C"], ["C", "D"], ["D", "E"]]>
type Test2 = DominoChain<[[3, 4], [4, 2], [2, 1]]>
type Test3 = DominoChain<[[3, null], [null, {}], [{}, undefined]]>
type Test4 = DominoChain<[[1, 2]]> // ok 
type Test5 = DominoChain<[]> // ok

type Test6 = DominoChain<[[1, 2], [3, 4],]> // never

const domino = <
  Elem extends string,
  Tuple extends Elem[],
  List extends [...Tuple][]
>(list: [...List] & DominoChain<[...List]>) => list

domino([["A", "B"], ["B", "C"], ["C", "D"], ["D", "E"]]) // ok
domino([[1, 2], [3, 4],]) // error

Lekplats

Last - hämtar sista elementet i tuplen Head - hämtar första elementet i tuplen

IsChainable - kontrollerar om det sista elementet i nuvarande tuppel utvidgar det första elementet i nästa tuppel och vice versa. Ganska enkelt.

DominoChain - itererar genom en lista av tupler och anropar IsChainable på varje tuppel.

Det är mycket kod men det är faktiskt inte så komplicerat när man väl förstår det.

Svar #2

Med tipset från @captain-yossarian att du behöver lägga till en ytterligare funktion eller konstant för att denna typkontroll ska fungera, skapade jag denna lösning:

// Definition of the domino brick
type Domino<A = any, B = any> = readonly [A,B]

// The definition of the array wiht matchin domino bricks
type DominoChain<T extends readonly Domino[], FIRST = any> =
    T extends readonly [Domino<any, infer A>, ... infer REST]
        ? (REST extends Domino[] ? readonly [Domino<FIRST, A>, ...DominoChain<REST, A>] : never)
        : readonly []

// This avoids type inference at an undesired place
type NoInfer<T> = [T][T extends any ? 0 : never];

// This function must be used to compare a inferred type against 
// the definition of DominoChain
function dominoTest<T extends readonly Domino[]>(domino: T & DominoChain<T>): NoInfer<T> {
    return domino
}
dominoTest(<const>[ ["A","B"], ["B","C"], ["C","D"], ["D","E"] ]) // OK
dominoTest(<const>[ [3, 4], [4, 2], [2, 1] ]) // OK
dominoTest(<const>[ [3, null], [null, {}], [{}, undefined] ]) // OK
dominoTest(<const>[ [1, 2] ]) // OK
dominoTest(<const>[ ]) // OK
dominoTest(<const>[ [1, 2], [3, 4] ]) // error
// TS2345: Argument of type 
// 'readonly [readonly [1, 2], readonly [3, 4]]' 
// is not assignable to parameter of type 
// 'readonly [Domino<any, 2>, Domino<2, 4>]'.   
// Type at position 1 in source is not compatible with type at position 1 in target.     
// Type '3' is not assignable to type '2'.

Uppdatering: Förbättrade det ursprungliga kodexemplet