Inaccurate inference of pipe and compose function types within TypeScript

I have created functions called pipe and compose which can combine two functions into a new one, with the only difference being the order in which they are called when applied to an argument.

Although both functions are generic, I am having trouble ensuring that the resulting function is strongly typed in TypeScript.

Here are my questions:

  1. Is the incorrect inference of the function type a limitation of the language, or am I making a mistake in my implementation?
  2. If it is possible in TypeScript, how can I correct this issue?
function pipe<X, Y, Z>(first: (x: X) => Y): (f2: (y: Y) => Z) => (x: X) => Z {
  return function (second: (y: Y) => Z): (x: X) => Z {
    return function (x: X): Z {
      return second(first(x));
    };
  }
}

function compose2<X, Y, Z>(second: (y: Y) => Z): (f2: (x: X) => Y) => (x: X) => Z {
  return function (first: (x: X) => Y): (x: X) => Z {
    return function (x: X): Z {
      return second(first(x));
    };
  }
}

Usage

function numToStr(n: number): string { return n.toString(); }
function strToLen(s: string): number { return s.length; }

// Currently inferred as (x: number) => unknown
// Expected to be (x: number) => number
const f1 = pipe(numToStr)(strToLen);

// Currently inferred as (x: unknown) => number
// Expected to be (x: number) => number
const f2 = compose(strToLen)(numToStr);

Answer №1

Instead of explicitly defining the return type for each function, it is beneficial to let TypeScript infer it and ensure that generic parameters are utilized at the appropriate time:

function pipe<X, Y>(first: (x: X) => Y) { // <-- Deferring <Z> here is too early
  return function <Z>(second: (y: Y) => Z) { // <-- Need to specify <Z> here
    return function (x: X) {
      return second(first(x));
    };
  }
}

function compose<Y, Z>(second: (y: Y) => Z) {// <-- Avoid specifying <X> here is too early
  return function <X>(first: (x: X) => Y) {// <-- Need to specify <X> here
    return function (x: X): Z {
      return second(first(x));
    };
  }
}

function numToStr(n: number): string { return n.toString(); }
function strToLen(s: string): number { return s.length; }

const f1 = pipe(numToStr)(strToLen);
const f2 = compose(strToLen)(numToStr);

The issue arises in the first call pipe(numToStr) where TypeScript infers x as number from n and Y as string from the return, but since there is no Z specified at this point, it defaults to unknown. TypeScript does not re-infer it later when the second call occurs, hence it remains as unknown.

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

Increasing the value of a TypeScript variable using ngFor in an HTML template can enhance its usefulness

Can you dynamically increase a variable declared in your TypeScript file for each element generated by an ngFor loop in your HTML file? <tbody> <tr *ngFor="let item of invoiceItems"> <td>{{item.ItemName}}</td> <td>{ ...

What are the two different ways to declare a property?

I am trying to update my interface as shown below interface Student{ Name: String; age: Number; } However, instead of the current structure, I would like it to be like this interface Student{ Name: String; age | DOB: Number | Date; } This means t ...

How to Animate the Deletion of an Angular Component in Motion?

This stackblitz demonstration showcases an animation where clicking on Create success causes the components view to smoothly transition from opacity 0 to opacity 1 over a duration of 5 seconds. If we clear the container using this.container.clear(), the r ...

Accessing the ViewModel property of a parent component from the ViewModel of its child in Aurelia

Having a scenario with two distinct components: <parent-component type="permanent"> <div child-component></div> </parent-component> class ParentComponentCustomElement { @bindable public type: string = "permanent"; } clas ...

Upgrading from Angular 5 to 6: Embracing the RxJS Changes without the crutch of rxjs

Currently, I am facing the challenging task of migrating a project from Angular 5.2.11 to version 6.0.0. The main issue I'm encountering is with RxJS 6 (which is essential for Angular versions above 6). Here's an example of one of the errors that ...

TypeScript maintains the reference and preserves the equality of two objects

Retrieve the last element of an array, make changes to the object that received the value, but inadvertently modify the original last position as well, resulting in both objects being identical. const lunchVisit = plannedVisits[plannedVisits.length ...

What is the best way to incorporate sturdy data types into the alternative function for this switch statement

const switchcase = (value, cases, defaultCase) => { const valueString = String(value); const result = Object.keys(cases).includes(valueString) ? cases[valueString] : defaultCase; return typeof result === 'function' ? result() : r ...

Activate the event when the radio button is changed

Is there a way to change the selected radio button in a radio group that is generated using a for loop? I am attempting to utilize the changeRadio function to select and trigger the change of the radio button based on a specific value. <mat-radio-group ...

What is the best way to apply a filter to an array of objects nested within another object in JavaScript?

I encountered an issue with one of the API responses, The response I received is as follows: [ {type: "StateCountry", state: "AL", countries: [{type: "County", countyName: "US"}, {type: "County", countyNa ...

Can you explain the distinction between Reflect.getMetadata and Reflect.getOwnMetadata?

Just like the title says, the reflect-metadata API comes with a method called getMetadata and another called getOwnMetadata. Can you explain the distinction between them? The same question applies to hasOwnMetadata, and so on. ...

How to effectively merge DefaultTheme with styled-components in TypeScript?

I am facing an issue with integrating a module developed using styled-components that exports a theme. I want to merge this exported theme with the theme of my application. In my attempt in theme.ts, I have done the following: import { theme as idCheckThe ...

Prisma designs a personalized function within the schema

In my mongo collection, there is a field named amount. My requirement is to have the amount automatically divided by 100 whenever it is requested. In Django, this can be achieved with a custom function within the model. Here's how I implemented it: cl ...

After using apt to install tsc, I find myself in a dilemma on how to either delete or upgrade it

After realizing I was missing Typescript on my server, I attempted to run the 'tsc' command. However, I received a message suggesting I use 'apt install tsc' instead. Without much thought, I executed the command. Normally, I would insta ...

Guide to simulating a function using .then within a hook

I am using a function that is called from a hook within my component. Here is my component: ... const handleCompleteContent = (contentId: string) => { postCompleteContent(contentId, playerId).then(data => { if (data === 201) { ... The caller ...

TypeScript: Error - .map() is an Invalid Function

I have come across numerous questions similar to mine, but the vast majority of them pertain to an Observable, which is not the issue I am facing. The code snippet in question looks like this: selectedItems: Item[] = null; selectedDate: Date = null; subm ...

When the page hosted on Vercel is reloaded, `getStaticProps` does not function as expected

I'm currently working on a nextjs project and running into an issue where one of the pages returns a 404 error when it's reloaded. Within the page directory, I am using getStaticProps. pages - blogs.tsx - blog/[slug].tsx - index.tsx ...

How to prevent redundant object declarations when passing parameters in TypeScript?

Motivation for Using Object Parameters One of the motivations behind using objects as function parameters is to allow the caller to clearly define arguments with specified field names, which can make code reviews easier. Challenge When Using Implements an ...

Determine the specific data types of the component properties in React Storybook using TypeScript

Currently, I am putting together a component in the storybook and this is how it appears: import React, { useCallback } from 'react'; import { ButtonProps } from './types'; const Button = (props: ButtonProps) => { // Extract the nec ...

Error: unable to locate module that was imported from

Every time I try to run the project using this command, an error pops up: > npm run build npm info using <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c7a9b7aa87fee9f1e9f0">[email protected]</a> npm info using ...

OpenTok Angular 6 encountered an error with code TS2314 stating that the generic type 'Promise<T>' needs to have 1 type argument specified

Issue in opentok.d.ts File: Error TS2314 npm version: 6.2.0 node: v8.10.0 Angular CLI: 6.2.3 Operating System: Linux x64 Angular Version: 7.0.0-beta.5 @opentok/client": "^2.14.8 ...