Exploring the Annotation of Higher-Order Functions in Typescript

I've been tackling a challenge of migrating a sizable JS code base to TypeScript, and I've hit a roadblock when trying to annotate a specific higher order function...

doStuff() takes fn1 as an argument and wraps it to return a new function that accepts all arguments except the first one from fn1. It's somewhat like this:

const fn1 = (arg_1, arg_2, arg_3, arg_n) => { return 'Hello World' }

const doStuff = (fn) => (...args) => {
    argument1 = getSomeStuffHere()
    return fn(argument1, ...args)
}

const test = doStuff(fn1)
let result = test('arg2', 'arg3', 'arg4')

It's important to note that there is just one doStuff() but numerous fnX() functions with different numbers of arguments and type combinations that are wrapped by it. Ensuring that the functions created by doStuff have correct typings is crucial; "any => any" won't suffice!

After much trial and error, I've finally arrived at this solution:

// For testing purposes, here's a simplified example
type MagicObj = {}
const myMagicObject = {}

type Wrapped<T, R> =
    T extends [MagicObj, any, any, any, any] ? (a: T[1], b: T[2], c: T[3], d: T[4]) => R :
    T extends [MagicObj, any, any, any] ? (a: T[1], b: T[2], c: T[3]) => R :
    T extends [MagicObj, any, any] ? (a: T[1], b: T[2]) => R :
    T extends [MagicObj, any] ? (a: T[1]) => R :
    T extends [MagicObj] ? () => R :
    unknown;

const doStuff = <T extends any[], R>(fn: (...args: T) => R): Wrapped<T, R> => (...args) => fn(myMagicObject, ...args)

// Testing examples
const fn1 = (obj: MagicObj, p1: string, p2: boolean, p3: number): string => { return 'Hello World' }
const fn2 = (obj: MagicObj, p1: number, p2: string) => { return 'Hello Mars' }
const fn3 = (obj: MagicObj, p1: boolean) => { return 'Hello The Moon' }

const test1 = doStuff(fn1)
let result1 = test1('str', true, 123) 

const test2 = doStuff(fn2)
let result2 = test2(123, 'str')

const test3 = doStuff(fn3)
let result3 = test3(true)

This approach seems to be somewhat functional. The type hinting intellisense in VSCode is displaying the expected types for the test1, 2, 3, and result variables at the end of the example. However, the function returned by doStuff

(...args) => fn(myMagicObject, ...args)
, no matter how I try to annotate it, consistently triggers an error similar to
Type '(...args: any[]) => any' is not assignable to type 'Wrapped<T, R>'.ts(2322)

Is there any suggestion on how to achieve this?

Answer №1

To achieve what you're aiming for, I recommend writing the doStuff() function in this manner:

const doStuff = <T extends any[], R>(
  fn: (magicObject: MagicObj, ...args: T) => R
): ((...args: T) => R) => (...args) => fn(myMagicObject, ...args);

This code snippet essentially transforms a function with its first argument as type MagicObj into another function of the same type, but without that initial MagicObj parameter. The implementation is designed to handle multiple parameters through the use of rest tuples.

Does this solution meet your requirements? Feel free to give it a try and good luck with your project!

Access the code 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

Automatic type inference for functions in TypeScript with arguments

I am looking to define an interface with the following structure: interface CheckNActSetup<D, C> { defs: (event: Event) => D, context: (defs: D) => C; exec: (context: C) => any[]; when: ((context: C) => boolean)[]; } and implement it usi ...

I can't find my unit test in the Test Explorer

I'm currently working on configuring a unit test in Typescript using tsUnit. To ensure that everything is set up correctly, I've created a simple test. However, whenever I try to run all tests in Test Explorer, no results are displayed! It appear ...

What is the best way to line up a Material icon and header text side by side?

Currently, I am developing a web-page using Angular Material. In my <mat-table>, I decided to include a <mat-icon> next to the header text. However, upon adding the mat-icon, I noticed that the icon and text were not perfectly aligned. The icon ...

What is preventing me from using property null checking to narrow down types?

Why does TypeScript give an error when using property checking to narrow the type like this? function test2(value:{a:number}|{b:number}){ // `.a` underlined with: "Property a does not exist on type {b:number}" if(value.a != null){ ...

Have you noticed the issue with Angular's logical OR when using it in the option attribute? It seems that when [(ngModel)] is applied within a select element, the [selected] attribute is unable to change

Within the select element, I have an option set up like this: <option [selected]=" impulse === 10 || isTraining " value="10">10</option> My assumption was that when any value is assigned to impulse and isTraining is set to true, the current o ...

Unable to achieve any outcomes with the @input directive in Angular

I've seen this question asked before with a lot of answers available through Google search. However, I'm still unable to figure out what I'm doing wrong in passing a value from one component to another. Here's what I have tried: < ...

Using TypeScript to convert a JSON date string into a Date object

The backend is sending me a JSON data structure that resembles the following: [{ "schedulingId": "7d98a02b-e14f-43e4-a8c9-6763ba6a5e76", "schedulingDateTime": "2019-12-28T14:00:00", "registrationDateTime": "2019-12-24T16:47:34", "doctorVie ...

Customize the MUISelect style within a universal theme

I need to override a specific style for numerous components, but it is currently only working for all components except the Select. Here is what I am attempting: MuiSelect: { styleOverrides: { select: { ...

What is the process of asynchronously importing a module's exported function using dynamic imports and then executing it?

When working with asynchronous callbacks in a promise promise.then(async callbackResultValue => { //here }) I experimented with this code: const browserd = await import('browser-detect'); if (typeof browserd === 'function') { ...

Troubleshooting Angular: Issues with Table Data Display and Source Map Error

I'm currently tackling a challenge in my Angular application where I am unable to display data in a table. When I fetch data from a service and assign it to a "rows" variable within the ngOnInit of my component, everything seems to be working fine bas ...

Unable to transfer an object into a component due to a subscribe restriction

I have encountered an issue: I am making a post request to save an object in the database. The request takes JSON input with the values of the object to be saved. After saving the object in the database, I need my servlet to return the saved object so that ...

I am encountering difficulties in accessing my component's property within the corresponding template while working with Angular 5

When I call an HTTP POST method to retrieve table names from the backend, I attempt to display them in the template using ngFor. However, the table names are not appearing on the screen. The tNames property is inaccessible in the template. As a beginner i ...

Data has not been loaded into the Angular NGX Datatable

As a beginner in Angular, I am struggling to set data from the module. ngOnInit() { this.populate(); } public populate() { this.productService.getAllProduct('6f453f89-274d-4462-9e4b-c42ae60344e4').subscribe(prod => { this. ...

Leveraging ts-jest in conjunction with create-react-app

Having trouble getting accurate coverage reports when running tests with the --coverage flag using create-react-app and react-scripts-ts for TypeScript. Is there a way to integrate ts-jest for correct coverage reports? Here's my jest configuration in ...

Using an object hierarchy in Typescript to create an if statement

Currently, I am attempting to create a logical statement using a hierarchy structure as shown below: if ((config.elementConfig.curve[0].dataset[0].splitBy = 'my discrete var')) {..... However, when implementing this statement, I encounter the er ...

Creating a dynamic dropdown menu triggered by a button click using Angular

I am a beginner in Angular (typescript) and I am facing some challenges in adding a new dropdown menu when a user clicks a button. My main struggle is creating additional attribute fields. I'm considering keeping track of the newly added dropdowns wit ...

Toggle the selection of all checkboxes in TypeScript

Help needed with creating a single select/deselect all checkbox in Typescript. The current code successfully selects all when checked but fails to deselect all when unchecked. selectAllLocations() { var selectAll = < HTMLInputElement > document. ...

Removing Angular Template space highlights in WebStorm can be done easily with a few simple steps

Is there a way to remove space highlights in Angular / TypeScript using WebStorm 2019? https://i.stack.imgur.com/vfudR.jpg Many thanks, Sean ...

Perform the subtraction operation on two boolean values using Typescript

I'm working with an array: main = [{ data: x, numberField: 1; }, { data: y, numberField: 2; }, { data: x, numberField: 3; }, { data: z, numberField: 4; }, { data: ...

Exploring TypeScript reflections within a specific namespace

(Building upon previous discussions about dynamically loading a TypeScript class) If I am currently within a namespace, is there a method to reference classes within the namespace in order to utilize 'reflection' to invoke their constructors? n ...