Home > database >  Defining an array type with matching pairs (like domino bricks) in typscript
Defining an array type with matching pairs (like domino bricks) in typscript

Time:11-10

I am trying to create a type alias in typescript to which all values are assignable that are arrays of Pairs and where each pair matches the succeeding pair like domino bricks do: Pair<A,B> matches Pair<C,D> if and only if B = C.

Example values:

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

Here is what I have so far, but it is not working:

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

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

I don't known how to properly define the matching constraint between two Domino pairs and I'm doing something wrong with the recursion part.

CodePudding user response:

It is not possible to express in typescript type system such restriction without validating generic argument.

However, it is possible to create a type utility which will validate your input.

Consider this example:

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

Playground

Last - obtains last element in the tuple Head - obtains first element in the tuple

IsChainable - checks whether last element in current tuple extends first element from the next tuple and vice versa. Pretty straightforward.

DominoChain - iterates through list of tuples and calls IsChainable on each tuple.

It is a lot of code but it is really not so complicated once you get it.

CodePudding user response:

With the hint from @captain-yossarian that you need to add an additional function or constant for this type checking to work, I created this solution:

type Domino<A = any, B = any> = readonly [A,B]
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)
        : T

function dominoTest<T extends readonly Domino[]>(domino: DominoChain<T>): 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'.


  • Related