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

Learn how to resubscribe and reconnect to a WebSocket using TypeScript

In my Ionic3 app, there is a specific view where I connect to a websocket observable/observer service upon entering the view: subscribtion: Subscription; ionViewDidEnter() { this.subscribtion = this.socket.message.subscribe(msg => { let confi ...

Error TS2394: Function implementation does not match overload signature in Typescript

Take a look at this code that seems to be causing headaches for the TypeScript compiler: use(path: PathParams, ...handlers: RequestHandler[]): this use(path: PathParams, ...handlers: RequestHandlerParams[]): this use(...handlers: RequestHandler[]): this u ...

Instructions on invoking a function from another Material UI TypeScript component using React

In this scenario, we have two main components - the Drawer and the AppBar. The AppBar contains a menu button that is supposed to trigger an event opening the Drawer. However, implementing this functionality has proven challenging. I attempted to use the li ...

Tips on filtering an array in a JSON response based on certain conditions in Angular 7

Looking to extract a specific array from a JSON response based on mismatched dataIDs and parentDataIDs using TypeScript in Angular 7. { "data":[ { "dataId":"Atlanta", "parentDataId":"America" }, { "dataId":"Newyork", ...

Reattempting a Promise in Typescript when encountering an error

I am currently working on a nodeJS application that utilizes the mssql driver to communicate with my SQL database. My goal is to have a unified function for retrieving a value from the database. However, in the scenario where the table does not exist upon ...

Contrast the differences between arrays and inserting data into specific index positions

In this scenario, I have two arrays structured as follows: arr1=[{room_no:1,bed_no:'1A'}, {room_no:1,bed_no:'1B'}, {room_no:2,bed_no:'2A'}, {room_no:3,bed_no:'3A'}, {room_no:3,bed_no:'3B ...

An effective method to utilize .map and .reduce for object manipulation resulting in a newly modified map

Here's an example of what the object looks like: informations = { addresses: { 0: {phone: 0}, 1: {phone: 1}, 2: {phone: 2}, 3: {phone: 3}, 4: {phone: 4}, 5: {phone: 5}, }, names: { 0 ...

Issue with updating initial state that is null in Redux Toolkit

Encountered an issue while using Redux Toolkit with Redux Persist. Unable to update the initial state of a user if it's null. The code consistently assigns null to the store regardless of passing parameters. import { createSlice, PayloadAction } from ...

Could it be possible for a Firestore onUpdate trigger's change parameter, specifically change.after.data() and change.before.data(), to become null or undefined?

Presented below is a snippet of my cloud function exports.onUpdateEvent = functions.firestore.document('collection/{documentId}') .onUpdate((change, context) => { const after: FirebaseFirestore.DocumentData = change.after.data(); const ...

Retrieve all services within a Fargate Cluster using AWS CDK

Is there a way to retrieve all Services using the Cluster construct in AWS CDK (example in TypeScript but any language)? Here is an example: import { Cluster, FargateService } from '@aws-cdk/aws-ecs'; private updateClusterServices(cluster: Clus ...

Experimenting with a VSCode extension that requires the act of launching a folder/workspace

Currently, I am developing a custom VSCode extension that considers the path of the file being opened within the workspace. To create a reproducible test scenario, I want to open the test folder itself in VSCode and then proceed to open the test file with ...

Issue with React not displaying JSX when onClick Button is triggered

I've recently started learning React and I'm facing a problem that I can't seem to figure out. I have a basic button, and when it's clicked, I want to add another text or HTML element. While the console log statement is working fine, th ...

The noUnusedLocal rule in the Typescript tsconfig is not being followed as expected

I am currently working on a project that utilizes typescript 3.6.3. Within my main directory, I have a tsconfig.json file with the setting noUnusedLocals: true: { "compilerOptions": { "noUnusedLocals": true, "noUnusedParameters": true, }, ...

Posting an array as form data in Angular Typescript: A step-by-step guide

Hello there, I'm currently developing an application using Angular 8 and integrating web.api within .net core 2.2. One of the challenges I encountered is dealing with multi-selectable checkboxes in a form that also includes "regular" inputs and file ...

Transform a string into a class in Typescript/Angular

In my application, I've created a reusable modal popup component that takes a string as input and dynamically loads other components based on that input. This approach allows me to use the same modal popup component for multiple modals in the app inst ...

Tips for showcasing the information from a JSON document in React

I have a JSON file stored statically in my public directory and I'd like to show its content within a React component (specifically NextJS). The goal is to simply render the JSON as it is on the page. import data from '../../public/static/somedat ...

How come TypeScript tuples support the array.push method?

In the TypeScript code snippet below, I have specified the role to be of Tuple type, meaning only 2 values of a specified type should be allowed in the role array. Despite this, I am still able to push a new item into the array. Why is the TS compiler not ...

Example TypeScript code: Use the following function in Angular 5 to calculate the total by summing up the subtotals. This function multiplies the price by the quantity

I have a table shown in the image. I am looking to create a function that calculates price* quantity = subtotal for each row, and then sum up all the subtotals to get the total amount with Total=Sum(Subtotal). https://i.stack.imgur.com/4JjfL.png This is ...

Error message: While running in JEST, the airtable code encountered a TypeError stating that it cannot read the 'bind' property of an

Encountered an error while running my Jest tests where there was an issue with importing Airtable TypeError: Cannot read property 'bind' of undefined > 1 | import AirtableAPI from 'airtable' | ^ at Object.&l ...

Implementing Angular 2 reactive forms checkbox validation in an Ionic application

I have implemented Angular Forms to create a basic form with fields for email, password, and a checkbox for Terms&Conditions in my Ionic application. Here is the HTML code: <form [formGroup]="registerForm" (ngSubmit)="register()" class="center"> ...