Tips for implementing curry and compose functions in Typescript 4

After diving into the world of variadic types, I was inspired to create something new. Now, I'm facing a challenge - how do I effectively utilize an array of functions?

This is my initial attempt:

function curry<T extends any[]>(fn: (...args: T) => any) {
  return function(...args: T) {
    return args.length >= fn.length
      ? fn(...args)
      : curry(fn.bind(undefined, ...args));
  }
}

However, when using fn.bind, I encounter the error message "The 'this' context of type '(...args: T) => any' is not assignable to method's 'this' of type '(this: undefined, ...args: any[]) => any'."

Any suggestions or solutions?

Answer №1

Your code implementation does not utilize variadic tuple types as described in the documentation. Instead, the 'curry()' function you are creating takes a partial parameter list and may return another function that handles the remaining parameters. This means that the initial tuple of parameters 'T' could be broken down into multiple pieces, which cannot be automatically inferred by context like the 'partialCall()' function from the documentation.

To make it work, you need to explicitly split the 'T' tuples into possible subtuples. Here is how the desired output type of 'curry()' can be represented:

type Curried<A extends any[], R> =
  <P extends Partial<A>>(...args: P) => P extends A ? R :
    A extends [...SameLength<P>, ...infer S] ? S extends any[] ? Curried<S, R>
    : never : never;

type SameLength<T extends any[]> = Extract<{ [K in keyof T]: any }, any[]>

In simpler terms, 'Curried' is a generic function that requires arguments of some tuple type 'P', constrained within 'Partial<A>'.

For tuples, 'Partial<A>' allows leaving out any suffix of the tuple (from somewhere to the end). For example, '[1, 2, 3]' can match 'Partial<[1,2,3,4,5,6,7]>', but '[1, 2, 4]' won't. Despite nuances with 'undefined', where '[1, undefined, 3]' matches 'Partial<[1,2,3,4,5,6,7]>', these cases should be handled accordingly if significant. The essence is to ensure that the arguments provided to 'Curried<A, R>' constitute a prefix of the 'A' tuple.

The returned type for 'Curried<A, R>' depends on the input prefix 'P'. If 'P' represents the whole tuple 'A', then the return type is simply 'R'. Otherwise, when splitting 'A' into 'P' and its suffix 'S', a new curried function of type 'Curried<S, R>' is returned.

The use and implementation process is demonstrated below:

function curry<A extends any[], R>(fn: (...args: A) => R): Curried<A, R> {
  return (...args: any[]): any =>
    args.length >= fn.length ? fn(...args as any) : curry((fn as any).bind(undefined, ...args));
}

Type assertions were heavily employed within 'curry()' due to the compiler's difficulty in verifying assignability to 'Curried<A, R>'. By leveraging type assertions, the responsibility shifts to ensuring correct implementation rather than compiler validation.

A sample testing scenario illustrates the functionality:

const fn = (a: string, b: number, c: boolean) => (a.length <= b) === c ? "yep" : "nope";

const cFn = curry(fn);
const val1 = cFn("") (1)(true);
console.log(val1); // yep

const val2 = cFn("", 1, true);
console.log(val2); // yep

const val3 = cFn() () () () ("", 1)() () (true); // yep

Upon testing, intentional errors underscore the compiler's error-checking capabilities:

// errors
cFn(1, 1, true); // error!
//  ~ <-- must be a string
cFn("", 1, true, false); // error!
//               ~~~~~ <-- Expected 0-3 arguments, but got 4
cFn("") (1) (false) (true); // error!
//~~~~~~~~~~~~~~~ <-- Function call invalid

The displayed errors align with expectations and confirm the correctness of the implementation.


Playground link to code

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

Unlocking Not Exported Type Definitions in TypeScript

Take a look at this TypeScript code snippet: lib.ts interface Person { name: string; age: number; } export default class PersonFactory { getPerson(): Person { return { name: "Alice", age: 30, } } } ...

Using the parameter value as a property name in the return type of a function in TypeScript: a guide

After creating a function that converts an object to an array where each element contains the ID of the object, I encountered a new requirement. The current function works great with the following code: const objectToArray = <T>(object: { [id: string ...

DataGrid parameters in Material UI are only considering the final value in the table

I am working with a Data Grid table containing user information, and I want to include a sub-menu of options in the last column that opens up a modal for each user. However, I am facing an issue where only the data from the final row in the table is being ...

Problem with Customization Functionality on Auto Rental Platform

For my current school project, I am developing a car rental website using Angular for the frontend and Python (Flask) for the backend. One of the main challenges I am facing is implementing a personalization feature. This feature should only display reserv ...

Error in sending data to the server via the specified URL: "Http failure response for http://localhost/post.php: 0 Unknown Error" and POST request to http://localhost/post.php failed with error code

Feeling a bit stuck trying to add data to my database. As a junior with PHP and Angular, I am using PHP via XAMPP and Angular 8. Is it possible to create separate files for the post and get methods in the PHP file? app.component.ts import { Component, O ...

How to retrieve TypeScript object within a Bootstrap modal in Angular

Unable to make my modal access a JavaScript object in the controller to dynamically populate fields. Progress Made: Created a component displaying a list of "person" objects. Implemented a functionality to open a modal upon clicking a row in the list. ...

Import and export classes in Typescript

I currently have two separate classes defined in two different files. //a.ts export class A{} //b.ts export class B{} My goal is to create a new file c.ts where I can import both classes seamlessly. import {A, B} from "c"; Instead of having to import ...

AngluarFire 2 authState function displaying null after refreshing the page

Currently, I am working on integrating Firebase with AngularFire 2 and facing an issue. The problem arises when I try to refresh the page, as the auth instance returns null. Below is the code snippet for my AuthService: Everything functions correctly, b ...

The React useState hook is not functioning as anticipated

I am having an issue with my useState hook that manages the state of selected checkboxes. The selected checkboxes should be instantly displayed in the UI within my filter, but currently they are only shown when the filter component is closed and reopened. ...

Utilize FastClick for improved speed and response on

I have been trying to use FastClick in my TypeScript project with FastClick.d.ts. My TSC configuration uses "module: commonjs" and I am bundling everything with Webpack. However, I am having trouble referencing FastClick properly. When I try to import Fas ...

Is it possible to eliminate the table borders and incorporate different colors for every other row?

Eliminating the table borders and applying color to alternate rows. Check out my code snippet: https://stackblitz.com/angular/dnbermjydavk?file=app%2Ftable-overview-example.ts. ...

Having trouble with Typescript accurately converting decimal numbers?

I am struggling with formatting decimals in my Typescript class. export myclass { deposit: number; } After converting my web API class to this Typescript class, my decimal amounts lose their additional zero. For example, 1.10 becomes 1.1. I want to keep ...

Issue regarding angularjs type definitions

I am facing an issue with installing typings for Angular and I need some guidance on how to resolve the error. Any suggestions or assistance would be greatly appreciated! Below is the error message that I encountered: ERROR in C:\Users\test&b ...

Unlock the key to connecting the output of one observable to another in rxjs

I have a procedure for saving users in the database. These are the steps I take: 1) First, I validate the request. 2) Next, I hash the password. 3) Finally, I store the user details in the users collection along with the hashed password. Below is the ...

Material-UI: Tips for aligning pagination button in the center

My attempt to implement Pagination using Material-UI went well, but now I am struggling to center the arrow buttons and page numbers. I initially tried centering them by wrapping them in a <div style={{textAlign: "center"}}>, however this ...

Implementing strict validation for Angular @Input by allowing only predefined values

I am facing an issue where I receive a string value as a parameter in a component, but I want to restrict the possible values that can be passed as a parameter, similar to using an enum. Currently, I have: @Input() type: string = ''; However, ...

What is the method for defining specific requirements for a generic type's implementation?

I am facing an issue with the following code snippet, where I am trying to restrict the pairing of Chart objects based on correct types for the data and options objects. However, despite my efforts, the TypeScript compiler is not throwing an error in the s ...

Angular 2 Form Error: Control Not Found

I'm facing an issue with Angular 2 Forms where adding more than one control seems to be getting ignored. Despite following numerous guides on how to properly implement this, none of the suggested methods seem to work in my case. In my template, I hav ...

Simulate internationalization for vue using jest

Currently, I am working on setting up jest unit tests for a Vue project within a complex custom monorepo. I am facing an issue with i18n, which I use for translation management in my application. The problem arises with the following code snippet for init ...

What exactly is the data type of setInterval in TypeScript?

If I want to define the type of a variable that will be used with setInterval in the following code snippet: this.autoSaveInterval = setInterval(function(){ if(this.car.id){ this.save(); } else{ this.create(); } ...