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
