Obtain the general type of a function parameter to use in overloads in TypeScript

While examining the code within the streaming-iterables package, I noticed a recurring pattern of defining an _fn and then overloading it with an fn, including a curried version. This practice of staying DRY seems to be prevalent in all functions exposed by the package.

Below is my attempt at creating a general currying overload implementation for this package:

type Leading<T extends any[]> = T extends [...infer I, infer _] ? I : never;
type Last<T extends any[]> = T extends [...infer _, infer I] ? I : never;

function curried<F extends (...args) => any>(
  fn: F
): {
  (...args: Parameters<F>): ReturnType<F>;
  (...args: Leading<Parameters<F>>): (curried: Last<Parameters<F>>) => ReturnType<F>;
} {
  return (...args) =>
    args.length == fn.length - 1
      ? curried => fn(...[...args, curried]) 
      : fn(...args);
}

This method works well until dealing with generics:

function a<T>(b: string, c: number, d: T[]) {
  return [b, c, ...d];
}

const f = curried(a);

f('hi', 42, [1, 2, 3]) // d: unknown[], expected: number[]
f('hi', 42) // curried: unknown[], expected: T[]

I understand that TypeScript currently lacks support for higher-order generics as highlighted here. However, it appears that there are potential workarounds mentioned elsewhere. Can these workarounds be integrated into this implementation successfully?

Answer №1

Exploring a solution utilizing a workaround that defines generic functions does not seem feasible in this case. While the workaround transforms a generic call signature into a generic type, it does not facilitate the transformation of one generic call signature into another, especially when dealing with multiple possible ones passed to curried(). Therefore, this approach is deemed unviable.


Although there is some support for inferring generic functions, its functionality is limited and fragile. It fails when generating overloaded functions, as seen in your implementation with the output of curried(). Refer to microsoft/TypeScript#33594 for further details. As of now, achieving overloads from curried() remains unattainable.


If we relax the requirement for overloads and focus on simply removing one parameter from the function's end, the call signature for curried() can be formulated like so:

declare function curried<I extends any[], L, R>(fn: (...args: [...I, L]) => R):
  ((...args: I) => (last: L) => R)

This can be verified through an example:

function z<T>(a: T, b: T, c: T) { return [a, b, c]; }

const cZ = curried(z);
// const cZ: <T>(a: T, b: T) => (last: T) => T[]

const cz2 = cZ(5, 6);
// const cz2: (last: number) => number[]

The essence of generality prevails here. The function z is generic, hence cZ also inherits this quality and allows for specifying T.


It's important to note that the demonstration does not involve your a function. Let's try using it:

function a<T>(b: string, c: number, d: T[]) { return [b, c, ...d]; }

const f = curried(a);
// const f: <T>(b: string, c: number) => (last: T[]) => (string | number | T)[]

const f2 = f('hi', 42);
// const f2: (last: unknown[]) => unknown[]

The resulting f proves to be generic, but lacks proper inference due to the absence of context determining the type parameter

T</code. Consequently, the compiler defaults to <code>unknown
.

To overcome this, you may manually specify T when calling

f</code, diminishing its generic nature slightly:</p>
<pre><code>const f3 = f<number>('hi', 42)
// const f3: (last: number[]) => (string | number)[]

An idealized generic output for curried(a) would redefine T scope within the returned function:

declare const f: (b: string, c: number) => <T>(last: T[]) => (string | number | T)[]

This adjustment would yield another generic function upon calling f():

const f2 = f('hi', 42);
// const f2: <T>(last: T[]) => (string | number | T)[]

Hence, eliminating instances of unknown:

const result = f2([1, 2, 3]);
// const result: (string | number)[]

Regrettably, the current TypeScript capabilities fall short in enabling automatic extension of generic type parameters beyond the initial scope. More advanced approaches such as higher kinded types, generic values, or existentially quantified generics are required to achieve this complex functionality.


In conclusion, while compromising on certain criteria may bring you closer to your goal, fully meeting all requirements might necessitate future TypeScript versions integrating enhanced manipulation features for generic function types.

Access the code on TypeScript Playground

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

When trying to render a list of items, an error occurs stating that the type void cannot be assigned to type React

Recently, I came across a coding snippet that aims to loop through an object's key-value pairs and display them in a list. import { PersoanaType } from "./PersoanaType"; type Props = { persoana: PersoanaType } export default function P ...

Get all instances of a particular attribute value within an interface

In my TypeScript code, I have defined an interface and two constants: interface Foo { readonly name: string; }; const FOO_1: Foo = { name: 'zing' }; const FOO_2: Foo = { name: 'baz' }; Is there a way to find all instances ...

Creating recursive interfaces in TypeScript allows for defining reusable structures that reference themselves within

Defining a Modular Type System Struggling to create a modular type system that meets my needs in an efficient manner. The core issue revolves around needing two variations of the same type system for frontend and backend purposes. This requirement stems f ...

Type checking generic dataclasses causes an incompatible type error

Attempting to perform type checking on a generic dataclass against a Protocol in Python 3.9 is resulting in an error that is puzzling to me. mypy example.py example.py:21: error: Argument "a" to "Y" has incompatible type "int" ...

Leveraging a Derived-Class Object Within the Base-Class to Invoke a Base-Class Function with Derived-Class Information

I have a situation where I need to access a method from a derived class in my base generic component that returns data specific to the derived class. The first issue I encountered is that I am unable to define the method as static in the abstract class! ...

What is the best way to loop through the keys of a generic object in TypeScript?

When I come across a large object of unknown size, my usual approach is to iterate over it. In the past, I've used generators and custom Symbol.iterator functions to make these objects iterable with a for..of loop. However, in the ever-evolving world ...

Although there may be some issues with tslint, the functionality is operating smoothly

I am in the process of learning tslint and typescript. Currently, I am facing an issue that I need help with. Could you provide guidance on how to resolve it? Despite conducting some research, I have been unable to find a solution. The relevant code snippe ...

What sets apart the function from the `=>` in TypeScript?

Currently, I am diving into the world of TypeScript and finding myself puzzled by the distinction between the keyword function and => (fat arrow). Take a look at the code snippet below: interface Counter { (start: number); interval: number; ...

What is the process of updating a displayed list by clicking on a button?

I have a requirement to showcase a list of individuals. Each individual has a set of team ids and roles. The displayed list should only include individuals with the role of "player" in the selected team. It seems to be functioning correctly for the first ...

Can you explain the significance of using curly braces in an import statement?

The TypeScript handbook has a section on Shorthand Ambient Modules, where an import statement is shown as: import x, {y} from "hot-new-module"; It doesn't explain why y is in curly braces in the above statement. If both x and y were inside the brace ...

Tips for retrieving information from a Vuetify modal window

Is it possible to retrieve data from a dialog in the VueJS Vuetify framework? Specifically, how can I access the username or password entered in the NewUserPopup.vue dialog from app.vue? App.vue = main template NewUserPopup.vue = Dialog template imported ...

AngularJS attempted to open $modal, but it encountered an error because the controller named <controllername> was not registered

Currently, I am in the process of enhancing a web application that was originally developed using AngularJS with typescript instead of JS. I have a controller set up with a specific template that I want to display in a modal using $modal.open. However, I ...

Using createStyles in TypeScript to align content with justifyContent

Within my toolbar, I have two icons positioned on the left end. At the moment, I am applying this specific styling approach: const useStyles = makeStyles((theme: Theme) => createStyles({ root: { display: 'flex', }, appBar: ...

What is the best way to merge imported types from a relative path?

In my TypeScript project, I am utilizing custom typings by importing them into my .ts modules with the following import statement: import { MyCoolInterface } from './types' However, when I compile my project using tsc, I encounter an issue wher ...

The MemoizedSelector<ICommonAppState, IMenuItemsObject[]> argument does not match the expected 'string' parameter type

I have implemented the use of createSelector from @ngrx/store in order to select an array of objects from my store. Despite successfully compiling my application, I encountered the following error: Argument of type 'MemoizedSelector<ICommonAppSta ...

The expression has been altered following verification. It previously read as 'model: 1777' but now states 'model: 2222'

I've been working on this HTML code that utilizes [(ngModel)] to update input values, and I want the Total, Subtotal, and Amount Paid fields to be automatically calculated when a change is made. However, I'm encountering some issues with this app ...

Prevent TypeScript from generalizing string literals as types

I have a constant Object called STUDY_TAGS with specific properties const STUDY_TAGS ={ InstanceAvailability: { tag: "00080056", type: "optional", vr: "string" }, ModalitiesinStudy: { tag: "00080061", type: " ...

The parameter type '==="' cannot be assigned to the 'WhereFilterOp' type in this argument

I'm currently working on creating a where clause for a firebase collection reference: this.allItineraries = firebase .firestore() .collection(`itinerary`); Here is the issue with the where clause: return this.allItiner ...

What is the issue with this bubblesort algorithm that is preventing it from

In my current coding situation, I am trying to organize an array based on the second element of each contained tuple. The process appears to be running smoothly until it reaches the last element. At that point, an exception is thrown. ERROR TypeError: C ...

What is preventing me from subscribing once more to the same EventEmitter after I have unsubscribed?

I have implemented a subscription in the ngOnInit() method of an Angular2 component and then unsubscribed in ngOnDestroy(). However, after reinitializing the component, I encounter the following error: ObjectUnsubscribedError: object unsubscribed The cod ...