Using the "compose" function with generic or anonymous arguments

Seeking a compose function that enables function composition. The ideal definition of compose should be type-safe, support any number of arguments, and handle generic or anonymous arguments accurately. However, I've encountered difficulties with the last requirement.

The current definition utilizes Recursive Conditional Types found in Typescript 4.1 Release Candidate:

type Compose<Fns extends any[]> =
    Fns extends [(...args: infer Args) => infer Return] ? (...args: Args) => Return :
    Fns extends [(...args: infer Args0) => infer Ret0, (arg: infer Arg1) => Ret1, ...infer Rest] ? (
        [Ret0, Arg1] extends [Arg1, Ret0] ? Compose<[(...args: Args0) => Ret1, ...Rest]> :
        never
    ) :
    never;

declare function compose<Fns extends ((...args: any[]) => any)[]>(
    ...fns: Fns
): Compose<Fns>;

Works flawlessly when all functions have fixed types:

declare function foo(x1: string, x2: number): number;
declare function bar(y: number): boolean;
declare function baz(z: boolean): string;

const foobarbaz = compose(foo, bar, baz); // (x1: string, x2: number) => string

An issue arises when one function passed to compose is generic:

declare function foo(x: string): number;
declare function bar<T>(foo: T): string;

const foobar = compose(foo, bar); // typed as `never`

In this case, foobar becomes never due to failure in the

[Arg1, Ret0] extends [Ret0, Arg1]
check within Compose. This occurs because T, and therefore Arg1, is inferred as
unknown</code. TypeScript might not automatically infer generic parameters here like it does in other cases.</p>
<p>The same issue arises with anonymous functions:</p>
<pre><code>declare function foo(x: string): number;

const foobar = compose(foo, x => x.toLocaleString()); // typed as `(x: string) => any`

In this scenario, x defaults to any instead of being implicitly

number</code based on the return value of <code>foo
.

These challenges are anticipated since the restriction on return values and next function's argument stems from Compose, which is evaluated after compose's return value. Trying different approaches hasn't been successful yet.

Despite attempts to redefine things for related functions, TypeScript continues to infer any or

unknown</code, leading to broken typing.</p>
<p>Feel free to reference my attempt:</p>
<pre><code>type Composable<Types extends any[]> = Tail<Types> extends infer Tail ? Tail extends any[] ? {
    [I in keyof Tail]: I extends keyof Types ? (arg: Types[I]) => Tail[I] : never;
} : never : never;

declare function compose<Fns extends Composable<T>, T extends any[]>(...fns: Fns): Compose<Fns>;

(Tail excludes the first member from the array type—here, Tail[I] refers to the item following Types[I])

Unfortunately, using any[] for T results in ((arg: any) => any)[], offering no solution.

While non-variadic versions of compose without conditional types may be considered, they are limited to handling predefined numbers of functions. Hence, exploring alternative solutions is crucial to avoid such limitations.

Answer №1

Starting from TypeScript 4.7, you have the option to assist the compiler using instantiation expressions.

It would be ideal if the compiler could keep track of the generic expression when deriving Parameters / ReturnType, but unfortunately this is not yet achievable to my knowledge.

declare function foo(x: string): number;
declare function bar<T>(foo: T): string;

const foobar = compose(foo, bar<string>);

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

Why is the value always left unused?

I am having an issue with handling value changes on focus and blur events in my input field. Here is the code snippet: <input v-model="totalAmount" @focus="hideSymbol(totalAmount)" @blur="showSymbol(totalAmount)" /> ...

Switching an application from Angular 5 to Angular 8 can lead to unexpected styling problems

My current project is based on Angular version 5.2, but we recently decided to upgrade it to Angular 8 along with updating the Angular CLI. After completing the necessary code refactoring for Angular 8, the application builds successfully without any error ...

Troubleshooting Nested Handlebars Problem

After creating a customized handlebar that checks for equality in this manner: Handlebars.registerHelper('ifEquals', (arg1, arg2, options) => { if (arg1 == arg2) { return options?.fn(this); } return options?.inverse(t ...

Stop unwanted clicking on inactive buttons in Angular

I want to make sure that disabled buttons cannot be clicked by users who might try to remove the disabled attribute and trigger an action. Currently, I have this code to handle the situation: <button [disabled]="someCondition" (click)="executeAction()" ...

Unable to determine the success of testing my catch block within the then clause

I'm a beginner in unit testing code and feeling a bit lost! I am attempting to throw an error for the function below to cover the catch block, but so far I have not been successful and I am not sure why. Function: public initialize(): Promise<thi ...

Angular displaying a slice of the data array

After following the example mentioned here, and successfully receiving API data, I am facing an issue where only one field from the data array is displayed in the TypeScript component's HTML element. Below is the content of todo.component.ts file im ...

Merging declarations fails to function properly following the release of the npm module

The file core.ts contains the definition of a class called AnyId. In another file named time.ts, more methods are added to the AnyId class. This is achieved by extending the type of AnyId using declaration merging: declare module './core' { in ...

Looking to organize a table in jhipster but unsure of the process. Can someone provide guidance on how

For the past week, I have been delving into jhipster and attempting to incorporate a sortable table. Could someone clarify how exactly the jhiSort and jhiSortBy functions operate? I am struggling to grasp the purpose of the predicate, ascending, and call ...

Making Amazon Cognito function seamlessly with angular2 and typescript

Having trouble getting Cognito’s Forgot password feature to work Using: Angular2+Typescript+Ionic New to this process, followed a Quickstart guide I found here No matter what I try, keep getting errors like Cannot read property 'CognitoUser' ...

What is the best way to link to a different module in typescript?

Encountering an issue related to the import and module features in TypeScript version 2.4.1. The problem arises from having two separate files: testAdd.ts: module mymodule { export class myClassAdd { static add(left: number, right: number): n ...

Create an interactive Angular form that dynamically generates groups of form elements based on data pulled from

I am currently developing an Angular application and working on creating a dynamic form using Angular. In this project, I am attempting to divide the form into two sections: Person Name and Personal Details. While I have successfully grouped fields for P ...

I continue to encounter the same error while attempting to deliver data to this form

Encountering an error that says: TypeError: Cannot read properties of null (reading 'persist') useEffect(() => { if (edit) { console.log(item) setValues(item!); } document.body.style.overflow = showModal ? "hidden ...

Placing gaps after every group of four digits

I am currently working with Ionic 4 and Angular 8, attempting to insert a space every 4 digits entered by the user. However, the function I have written seems to be malfunctioning, as it inserts a space after each action the user takes following 4 numbers. ...

Creating a unique RXJS pipe that takes in an observable and holds off until said observable satisfies a specific criterion

Check out the code snippet below: export function featureComplete(feature: BaseFeatureService) { return pipe( combineLatest([feature.loading$]), filter(([input, loading]) => !loading), map(([input, loading]) => { return input; ...

Setting up popover functionality in TypeScript with Bootstrap 4

Seeking assistance with initializing popovers using TypeScript. I am attempting to initialize each element with the data-toggle="popover" attribute found on the page using querySelectorAll(). Here is an example of what I have tried so far: export class P ...

Creating a custom Angular 4 module with Material and including the MdCommonModule

As an Angular 4 + Material developer, I recently created a custom library that has been presenting me with numerous challenges. One component in particular, the SearchComponent, seemed pretty straightforward at first. search.component.ts @Component({ sel ...

I am searching for answers to solve issues related to a JSON file

I am currently working on a tool that searches for matches in an input field by comparing the keywords entered by the user with a JSON. During my testing phase, I focused on using a single API that provides information about different countries and fortun ...

The parameter type '{ src: string; thumb: string; }' cannot be assigned to the 'never' type in the argument

Sample Typescript Code: I am experiencing issues with my code and cannot comprehend this error message- Argument of type '{ src: string; thumb: string; }' is not assignable to parameter of type 'never' _albums = []; constructor( ...

Error: Unable to locate the type definition file for the '@babel' package

I am currently working on a new project and here is the content of my package.json file. { "name": "dapp-boilerplate", "version": "1.0.0", "main": "index.js", "license": "MI ...

Error in typescript: The property 'exact' is not found in the type 'IntrinsicAttributes & RouteProps'

While trying to set up private routing in Typescript, I encountered the following error. Can anyone provide assistance? Type '{ exact: true; render: (routerProps: RouterProps) => Element; }' is not compatible with type 'IntrinsicAttribu ...