Combining generic types in Typescript by utilizing a recursive concatenation method within a monad

I recently created a Result monad that is nearly functioning :)

class Result<T> {
  private readonly value: T;
  private readonly error: Error;

  constructor(value?: T, error?: Error) {
    if (value) {
      this.value = value;
    }
    if (error) {
      this.error = error;
    }
  }

  static ok<T>(ok: T): Result<T> {
    return new Result<T>(ok);
  }

  static error<T>(err: Error): Result<T> {
    return new Result<T>(undefined, err);
  }

  isOk(): boolean {
    return this.value !== undefined;
  }

  isError(): boolean {
    return this.error !== undefined;
  }

  chain<U>(f: (t: T) => Result<U>): Result<U> {
    return this.isOk() ?
      f(this.value) :
      Result.error<U>(this.error);
  }

  map<U>(f: (t: T) => U): Result<U> {
    return this.isOk() ?
      Result.ok<U>(f(this.value)) :
      Result.error<U>(this.error);
  }

  concat<U>(aResult: Result<U>): Result<(T | U)[]> {
    return this.isOk() ?
      Result.ok<(T | U)[]>(Array().concat(this.value, aResult.getValue())) :
      Result.error(this.error);
  }

  getError(): Error {
    return this.error;
  }

  getValue(): T {
    return this.value;
  }
}

Instructions for using it:

 const r1 = Result.ok(76)
      .concat(Result.ok('ddd'))
      .concat(Result.ok(true))
      .map((i: (number | string | boolean)[]) => i);

In this case, I am combining three results: an integer, a string, and a boolean.

Following that, I plan to apply a transformation method on them by mapping.

The .map function requires the input as (number | string | boolean)[].

Unfortunately, an error message pops up:https://i.sstatic.net/IbztE.png

Although I understand the issue, I'm unsure about the solution.

The problem lies within the .concat function:

concat<U>(aResult: Result<U>): Result<(T | U)[]> {
        return this.isOk() ?
          Result.ok<(T | U)[]>(Array().concat(this.value, aResult.getValue())) :
          Result.error(this.error);
      }

Whenever there's concatenation, this method is called repeatedly—a form of recursion.

This is how it occurs in my scenario:

  1. first concatenation - U represents the string 'ddd', and T stands for the number 76. This returns a (number | string)[]

  2. second concatenation - U is already a (number | string)[] and now accepts a boolean. Instead of returning (number | string | boolean)[], TypeScript yields (boolean | (number | string)[])[]

Is there a way to modify the concat function to accept the signature (string | number | boolean)[]?

Any suggestions or solutions would be appreciated.

Answer №1

To return (TElement | U)[] if T is already an array, consider adding an extra overload for cases where this has a type of array:

concat<TElement, U>(this: Result<TElement[]>,  aResult: Result<U>): Result<(TElement | U)[]>
concat<U>(aResult: Result<U>): Result<(T | U)[]>
concat<U>(aResult: Result<U>): Result<(T | U)[]> {
  return this.isOk() ?
    Result.ok<(T | U)[]>(Array().concat(this.value, aResult.getValue())) :
    Result.error(this.error);
}

Alternatively, you can utilize a conditional type which will handle the different scenarios without requiring multiple overloads:

concat<U>(aResult: Result<U>): Result<( (T extends (infer TElement)[] ? TElement : T ) | U)[]> {
  return this.isOk() ?
    Result.ok<(T | U)[]>(Array().concat(this.value, aResult.getValue())) :
    Result.error(this.error) as any;
}

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

specifying a specific type in a declaration

In this scenario, my goal is to distinguish between different types when declaring a new type: type Schedule = { flag_active : boolean, } type Channel = { flag_archived : boolean } type CreateChangeLog = { from : null, to : Schedule | Channel } ty ...

The type of JSX element attribute `[...]` cannot be a combination of multiple types

When attempting the codes below in Typescript 2.1: // address.tsx ... interface Address { street: string; country: string; } interface CanadianAddress extends Address { postalCode: string; } interface AmericanAddress extends Address { zipCode: s ...

Trouble with a third-party library component not functioning properly on the server side in a Next.js environment

I've encountered a puzzling issue lately in my work. Recently, I started using the new NextJS v13 with React server components. I'm integrating it into a project that depends on a small private third-party library I created and shared among mul ...

What is the best way to perform type casting in Typescript? Can the 'as?' operator be used for this purpose?

This particular line of code is causing an issue: const oid: string | undefined = keyPath[0] This is because the keyPath array may contain elements of either number or string type. Type 'string | number' is not assignable to type 'string&ap ...

Getting an error in Typescript: 'Operator '!==' cannot be used with types' while working with enums

When comparing enums, I encountered a perplexing error message stating "Operator '!==' cannot be applied to types," whereas the same comparison with integer values did not result in an error: enum E { A, B, C, D, E, F } ...

Is Angular CLI incorrectly flagging circular dependencies for nested Material Dialogs?

My Angular 8 project incorporates a service class that manages the creation of dialog components using Angular Material. These dialogs are based on different component types, and the service class is designed to handle their rendering. Below is a simplifie ...

Unable to compile TypeScript files using gulp while targeting ES5

After starting my first Angular2 app, I encountered an issue where I couldn't use gulp to compile for es5. Below is the dependencies file: "dependencies": { "@angular/common": "2.0.0", "@angular/compiler": "2.0.0", "@angular/compiler-cli ...

Tips for displaying text on the bubbles of a vector map using the DevExpress library in an Angular and TypeScript environment to showcase counts or other relevant information

I managed to incorporate the devextreme angular 2 map into my demo project. My goal is to display the count of individuals with the name 'x' on the bubble as a label/text on the vector map, without the need for hovering to see a tooltip. I want t ...

Unable to register click event, encountering an error message stating, "co.console.log() is not a function."

I've been attempting to create a button that navigates to another page while passing an object in the parameters. However, I keep encountering an error message: "co. is not a function." It's perplexing because I receive the same error when tr ...

Struggling to send API POST request using Next.js

I'm relatively new to backend development, as well as Next.js and TypeScript. I'm currently attempting to make a POST request to an API that will receive a formData object and use it to create a new listing. My approach involves utilizing Next.js ...

Exploring Pan Gestures in Ionic 3

Looking for information on Pan events in Ionic 3 - not sure if they are related to Cordova or Angular 4? <div class="swipper" #swipper (panleft)="swipe( $event)" (panright)="swipe( $event)" (panend)="swipe( $event) " (panup)="swipe( $event) " (pandown) ...

Is there a way to limit a TypeScript function to only accept `keyof` values corresponding to fields of a specific type?

I'm currently working on developing a function that updates a field in an object if the passed-in value differs from the existing one. The code snippet I have so far is type KnownCountryKeys = "defaultCountry" | "country"; type IHa ...

Tips for enforcing lang="ts" in script tags using an ESLint rule in Vue

Seeking a method to ensure that all team members working on our code base utilize TypeScript for Single File Components. The components are all designed with TypeScript, therefore disabling JavaScript is a viable solution. I initially considered implement ...

Exploring the Observable object within Angular

As I delve into learning Angular through various tutorials, I encountered a perplexing issue regarding my console displaying an error message: ERROR in src/app/employee/employee.component.ts:17:24 - error TS2322: Type 'IEmployee' is not assignab ...

Creating a message factory in Typescript using generics

One scenario in my application requires me to define message structures using a simple TypeScript generic along with a basic message factory. Here is the solution I devised: export type Message< T extends string, P extends Record<string, any> ...

Utilizing classes as types in TypeScript

Why is it possible to use a class as a type in TypeScript, like word: Word in the provided code snippet? class Dict { private words: Words = {}; // I am curious about this specific line add(word: Word) { if (!this.words[word.term]) { this.wor ...

Generate an Observable<boolean> from a service function once two subscriptions have successfully completed

I am working on setting up a simple method to compare the current username with a profile's username in an Angular service. It is necessary for the profile username and user's username to be resolved before they can be compared. How can I create ...

The module rxjs/operators cannot be located

When trying to import rxjs/operators in my Angular project, I use the following syntax: import { map } from 'rxjs/operators'; However, this results in the following error message: map is declared but its value is never read. Cannot find modu ...

Unraveling the mystery of nested optional indexes in interfaces

Discover the interface outlined on this TS playground export type GetTestimonialsSectionQuery = { __typename?: 'Query', testimonialsSection?: { __typename?: 'TestimonialsSection', id: string, testimonials: Array< ...

How can users create on-click buttons to activate zoom in and zoom out features in a Plotly chart?

I am currently working on an Angular application where I need to implement zoom in and zoom out functionality for a Plotly chart. While the default hoverable mode bar provides this feature, it is not suitable for our specific use case. We require user-cr ...