Creating a data structure that consists of pairs of elements, inspired by the alignment of domino bricks, using TypeScript syntax

My goal is to establish a type alias in TypeScript that allows all values which are arrays of Domino pairs, where each pair connects like domino bricks: Pair<A,B> connects with Pair<C,D> only if B = C.

For example:

const chain1: DominoChain = [ ["A", "B"], ["B", "C"], ["C", "D"], ["D", "E"] ] // Accepted
const chain2: DominoChain = [ [3, 4], [4, 2], [2, 1] ] // Accepted
const chain3: DominoChain = [ [3, null], [null, {}], [{}, undefined] ] // Accepted
const chain4: DominoChain = [ [1, 2] ] // Accepted
const chain5: DominoChain = [ ] // Accepted
const chain6: DominoChain = [ [1, 2], [3, 4] ] // Not accepted due to 2 != 3. Compiler error expected.

I have made some attempts but it's not functioning as intended:

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

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

I am unsure how to correctly enforce the matching constraint between two Domino pairs and I suspect there may be an issue with the recursive aspect.

Answer №1

To enforce constraints in the typescript type system, generic arguments need to be validated.

A type utility can be created to validate input, as demonstrated in the following 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 - retrieves the last element in a tuple Head - retrieves the first element in a tuple

IsChainable - verifies if the last element in the current tuple extends the first element of the next tuple, and vice versa.

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

Although the code may seem extensive, it is actually straightforward once understood.

Answer №2

Upon receiving a clever hint from @captain-yossarian regarding the necessity of incorporating an extra function or constant for successful type checking, I devised the following solution:

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

// Defining an array of matching 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 []

// Preventing undesired type inference
type NoInfer<T> = [T][T extends any ? 0 : never];

// Using this function to compare an inferred type with 
// 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'.

Update: Enhanced the initial code snippet

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Nested self-referencing in Typescript involves a structure where

Please note that the code below has been simplified to highlight a specific issue. The explanation before the code may be lengthy, but it is necessary for clarity. Imagine I have a Foo class that represents a complex object. interface Config { bars:{ ...

Angular2 Cache: Enhance Your Application's Performance

Currently seeking a cache solution for my Angular2 application. Imagine we have a massive collection of Movie objects stored on a server, too many to fetch all at once. The server offers a REST endpoint: getMovie(String id) On the client side, I need a s ...

Retrieve the user information from Auth0 within the NestJS application

I am currently working on implementing Auth0 authorization in NestJS, but I am unsure of how to retrieve the user's data within the callback URL handler. In a normal express function, this issue could be resolved using the following code. The passpor ...

Do const generics similar to Rust exist in TypeScript?

Within TypeScript, literals are considered types. By implementing const-generics, I would have the ability to utilize the value of the literal within the type it belongs to. For example: class PreciseCurrency<const EXCHANGE_RATE: number> { amount ...

Utilizing React Higher Order Components with TypeScript: can be initialized with a varied subtype of restriction

I am currently working on creating a Higher Order Component (HOC) that wraps a component with a required property called value, while excluding its own property called name. import React, { ComponentType } from 'react'; interface IPassThro ...

Having trouble opening a JPEG file that was generated using the Writefile Api in Ionic-Cordova

Currently, I am using the writeFile API to create a JPEG image. The process is successful and the image is stored in the directory as expected. However, when I try to open the file manually from the directory, I encounter an error message saying "Oops! Cou ...

Do Angular 2 component getters get reevaluated with each update?

What advantages do getters offer compared to attributes initialized using ngOnInit? ...

Can a generic type be utilized to instantiate an object?

In my code, I have a class named Entity as shown below: class Entity { constructor(readonly someValue: string) {} someFunction() {} } Now, I am trying to create a class that will handle these entities and be able to create instances of them. In or ...

Can someone provide a description for a field within typedoc documentation?

Here is the code snippet: /** * Description of the class */ export class SomeClass { /** * Description of the field */ message: string; } I have tested it on the TSDoc playground and noticed that there is a summary for the class, but not for it ...

Removing empty options from a select dropdown in Angular 9

In the process of working with Angular 9, I am currently in the process of constructing a dropdown menu that contains various options. However, I have encountered an issue where there is a blank option displayed when the page initially loads. How can I eli ...

How to Extract the Specific Parameter Type from a Function in Typescript

After generating a client for an API using typescript-node, I encountered the following code: export declare class Api { getUser(username: string, email: string, idType: '1298' | '2309' | '7801') } I need to access the ...

Updating the page dynamically in React/Redux by making API calls based on user submissions

My current task involves calling an API with Redux, triggering the call based on a form submission. If the query is empty, it should return all lists; otherwise, it should only return lists that match the query. // List.tsx import React, { useEffect, useS ...

Is there a workaround in TypeScript to add extra details to a route?

Typically, I include some settings in my route. For instance: .when('Products', { templateUrl: 'App/Products.html', settings: { showbuy: true, showex ...

Instructions for including a class are ineffective

I am trying to dynamically add a class to a div based on two conditions. To achieve this, I have created a custom directive as shown below: import { Directive, HostBinding, Input } from '@angular/core'; @Directive({ selector: '[confirmdia ...

Using Angular CLI with ES6 instead of TypeScript for your development needs can offer a

Is there a way to utilize an ES6 transpiler such as Babel instead of TypeScript in an Angular CLI project? Are there any specific flags for specifying the script language, similar to using --style? Thank you. ...

Issues with Angular 4 Rxjs subject subscription functionality

My application consists of a shared service named data.service.ts, which contains the following code: public pauseProjectTask$: Subject<any> = new Subject<any>(); pauseTaskProject(taskData, type){ this.pauseProjectTask$.next(taskData); ...

Displaying a collection of objects in HTML by iterating through an array

As someone new to coding, I am eager to tackle the following challenge: I have designed 3 distinct classes. The primary class is the Place class, followed by a restaurant class and an events class. Both the restaurant class and events class inherit core p ...

What is the best way to execute a function from a parent element within the child element?

Currently, I am working with two components - Navbar (parent) and Setting Menu (Child). const Navbar: React.FC = () => { const classes = useStyles(); const [randomState, setrandomState] = useState(false); const randomStateFunction = () =>{ ...

Tips for dynamically implementing a pipe in Angular 5

In my Angular application, I have implemented a filter using a pipe to search for option values based on user input. This filter is applied at the field level within a dynamically generated form constructed using an ngFor loop and populated with data from ...

Unable to modify the border-radius property of Material UI DatePicker

I'm having difficulties setting rounded borders for my DatePicker component from @mui/x-date-pickers and Material UI V5. Here is the intended look I am aiming for: https://i.stack.imgur.com/c1T8b.png I've tried using styled components from Mat ...