Discovering the Type Inference of Function Composition Method (Chaining) in TypeScript

I'm working on implementing a function that can add a chainable method for function composition. Here's what I have so far:

Also see: TypeScript playground

{
  const F = <T, U>(f: (a: T) => U) => {
    type F = {
      compose: <V, >(g: (b: U) => V) => (((a: T) => V) & F);
    };
    return Object.defineProperty(f, "compose", {
      configurable: true,
      value: <V,>(g: (b: U) => V) => F((a: T) => g(f(a)))
    }) as ((a: T) => U) & F
  };
  //--------------------------------
  const f1 = (a: number) => a + 1;
  const f2 = (a: number) => a.toString();
  const identity = <T,>(a: T) => a;
  //--------------------------------
  const F2 = F(f2);
  // ((a: number) => string) & F  // good
  const F12 = F(f1).compose(f2);
  // ((a: number) => string) & F  // good
  //--------------------------------
  const F2i = (F2).compose(identity);
  // ((a: number) => string) & F  // good
  const f12i = (F12).compose(identity);
  // ((a: number) => number) & F  // bad  why??
  //--------------------------------
  const fi1 = F(identity).compose(f1);
  /* ts(2345) error
    Argument of type '(a: number) => number' is not assignable to parameter of type '(b: unknown) => number'.
        Types of parameters 'a' and 'b' are incompatible.
          Type 'unknown' is not assignable to type 'number'.
    const f1: (a: number) => number
  */
}

In this example code, we have three basic functions; f1, f2, and identity.

F is the function that extends a specified function with a method for function composition.

I've made it work somehow, but I've encountered at least two issues.

1.

When using F for f2, the type of F2 (

((a: number) => string) & F
) is as expected. Similarly, when using F for f1 and composing with f2, the type of F12 is also as expected.

However, the type of (F12).compose(identity) turns out to be

((a: number) => number) & F
, which is unexpected. I've been trying to figure out why this happens without success.

If you have any advice, I'd appreciate it. Thanks!

EDIT:

Please note that the functions should not be wrapped in an Object, and my goal is to provide a compose method directly to the functions:

 const f = (a: number) => a + 1;
  const fg = f.compose(g);

  //not
  {
    f: f,
      compose:someFunction
  }

EDIT: Regarding issue #2, I created a separate question based on comments from @jcalz:

Is there any workaround for ts(2345) error for TypeScript lacks higher kinded types?

2.

I'm encountering a ts(2345) error, and the error message is unclear to me, making it difficult to resolve. Any ideas on how to fix this would be greatly appreciated.

Answer №1

If you take a closer look at the type of const F, you will notice an issue when you expand it:

const alsoF: <T, U>(f: (a: T) => U) =>
  ((a: T) => U) & {
    compose: <V>(g: (b: U) => V) => (((a: T) => V) & {
      compose: <V>(g: (b: U) => V) => ...

The problem arises because each call to compose() is expecting a callback parameter of type U, while you actually want it to be of type

V</code, which is the return type of the previous <code>compose()
.

To resolve this, we need to use generics in TypeScript to keep track of both the original type and the current type. The updated definition of F<T, U> would help in maintaining this relationship:

interface F<T, U> {
  (a: T): U;
  compose<V>(g: (b: U) => V): F<T, V>;
}

By implementing this concept, we can now accurately reflect the type of the callbacks passed to compose() for proper chaining.


This approach ensures that the returned types are aligned with expectations. Feel free to experiment further using the code provided!

Try out the updated implementation on TypeScript Playground here

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

Increase the totalAmount by adding the product each time

Can someone help me understand why the totalAmount shows as 20 when I add a product? Also, why doesn't it increase when I try to increment it? Any insights would be appreciated. Thank you. ts.file productList = [ { id: 1, name: 'Louis ...

Is it possible to use v-if in conjunction with a style tag to specify a different source file? Alternatively, is there a more efficient method I

I attempted the example provided below, but unfortunately, it did not function as expected. The reason behind my endeavor is that adding numerous modifiers (--tuned) to achieve the desired outcome seemed impractical. Therefore, I decided to try and link ...

Using debounceTime and distinctUntilChanged in Angular 6 for efficient data handling

I recently came across a tutorial on RxJS that demonstrated the use of debounce and distinctUntilChanged. I'm trying to implement it in Angular 6, but I'm facing some challenges. Here is the code from the tutorial: var observable = Rx.Observabl ...

What is the procedure for entering the output generated by genMockFromModule when creating a custom mock in Jest?

Looking at my orders directory structure, I have a function in the orders/helpers file that I want to test using a manual Jest mock. orders __mocks__ helpers.ts __tests__ orders.ts helpers.ts orders.ts The orders/helpers.ts file contains ...

Intercepting HTTP requests in Angular, but not making any changes to the

Working on Angular 13, I am trying to attach a JWT token to the headers in order to access a restricted route on the backend. However, after inspecting the backend, it seems that the JwtInterceptor is not modifying the HTTP request headers. I have included ...

The authService is facing dependency resolution issues with the jwtService, causing a roadblock in the application's functionality

I'm puzzled by the error message I received: [Nest] 1276 - 25/04/2024 19:39:31 ERROR [ExceptionHandler] Nest can't resolve dependencies of the AuthService (?, JwtService). Please make sure that the argument UsersService at index [0] is availab ...

Issue: The element '[object Object]' is of type 'object', which is not supported by NgFor. NgFor only works with Iterables like Arrays. - Problem encountered in an Ionic Project

I'm currently working on retrieving my user's username from Firebase Firestore Database using Ionic and AngularFire. I have implemented the valueChanges() method to obtain the observable and am trying to process it using an async pipe. However, u ...

Can you explain the distinction between 'rxjs/operators' and 'rxjs/internal/operators'?

When working on an Angular project, I often need to import functionalities like the Observable or switchMap operator. In such cases, there are two options available: import { switchMap } from 'rxjs/operators'; or import { switchMap } from ' ...

What is the importance of including "declare var angular" while working with Typescript and AngularJS?

I've been working on an AngularJS 1.7 application that's coded entirely in TypeScript, and there's always been one thing bothering me. Within my app.module.ts file, I have this piece of code that doesn't sit right with me: declare va ...

The ordering of my styles and Material-UI styles is causing conflicts and overrides

Greetings fellow developers! I'm currently facing an issue with my custom styles created using makeStyles(...). The problem arises when I import my styles constant from another module, and the order of the style block is causing my styles to be overr ...

Applying Validators manually in Angular 2 beta 17

We are currently working on a legacy project that needs to be maintained until the final version with angular-final is deployed. Once we upgrade to the final version, I will be able to easily apply conditional Validators using: this.myForm.controls[&apos ...

Why does the Angular page not load on the first visit, but loads successfully on subsequent visits and opens without any issues?

I am currently in the process of converting a template to Angular that utilizes HTML, CSS, Bootstrap, JavaScript, and other similar technologies. Within the template, there is a loader function with a GIF animation embedded within it. Interestingly, upon ...

Is there a way to specifically target the MUI paper component within the select style without relying on the SX props?

I have been experimenting with styling the Select MUI component using the styled function. I am looking to create a reusable style and move away from using sx. Despite trying various methods, I am struggling to identify the correct class in order to direct ...

Guide on removing a key from an object in TypeScript

My variable myMap: { [key: string]: string[] } = {} contains data that I need to modify. Specifically, I am trying to remove a specific value associated with a certain key from the myMap. In this case, my goal is to delete value1 from myMap[Key1]. Despit ...

How to use attributes in Angular 2 when initializing a class constructor

Is there a way to transfer attributes from a parent component to the constructor of their child components in Angular 2? The process is halfway solved, with attributes being successfully passed to the view. /client/app.ts import {Component, View, bootst ...

The componentWillReceiveProps method is not triggered when a property is removed from an object prop

Just starting out with React, so I could really use some assistance from the community! I am working with a prop called 'sampleProp' which is defined as follows: sampleProp = {key:0, value:[]} When I click a button, I am trying to remo ...

Utilizing Vue and Typescript for efficient dependency injection

After attempting to use vue-injector, I encountered an issue as it was not compatible with my version of Vue (2.6.10) and Typescript (3.4.5). Exploring other alternatives, there seem to be limited options available. Within the realm of pure typescript, t ...

An issue with the "req" parameter in Middleware.ts: - No compatible overload found for this call

Currently, I am utilizing the following dependencies: "next": "14.1.0", "next-auth": "^5.0.0-beta.11", "next-themes": "^0.2.1", In my project directory's root, there exists a file named midd ...

Guide to updating the canvas in Chart.js based on a user-defined x-axis range

What I currently have: My chart.js canvas displays values on the x-axis ranging from 1 to 9. Users can input a new range to view a different scope, with default limits set at start = 3 and end = 6 in my repository. I already have a function that restrict ...

Crafting a Retro Style

I have an interface called Product which includes properties such as name, and I want to track changes to these products using a separate interface called Change. A Change should include the original Product as well as all of its properties prefixed with t ...