What is the process in TypeScript for defining a custom variation of a generic function?

Suppose we have a generic function:

const f1 = <T>(x: T) => console.log(x)

We can then create a specialized version for f1, like this:

const f2 = (x: number) => f1(x)

If we try to call f2 with an argument of type string, TypeScript will throw an error.

However, it's important to note that f2 is essentially just another function that calls f1.

The question remains, is there a way to define f2 as a specialized version of f1? Perhaps something along these lines:

const f2 = f1<number> // unfortunately, this syntax doesn't work

Answer №1

Let's modify the example function because the type <T>(x: T)=>void doesn't provide any significant use with the generic type parameter. It behaves similarly to (x: unknown)=>void, a concrete function type. Instead, we'll explore a function of type <T>(x: T)=>T, which maintains the input type and produces an output value of the same type. This is where the difference lies, as (x: unknown)=>unknown wouldn't suffice:

const f1 = <T>(x: T) => (console.log(x), x);
// const f1: <T>(x: T) => T

const n: number = 6;
const sixNumber = f1(n); // const sixNumber: number

const s: string = "6";    
const sixString = f1(s); // const sixString: string

Your aim is to treat the generic function f1 as if it were a specific function f2 of type (x: number)=>number. By doing this, you are widening the type of f1, since every function of type <T>(x: T)=>T can also be considered a function of type (x: number)=>number, but not the other way around. The compiler acknowledges this widening and by manually annotating f2 with the extended type, you can achieve your goal:

const f2: (x: number) => number = f1; // no error
const sixNumberOkay = f2(n); // okay
const sixStringNotOkay = f2(s); // error! string is not a number

This process works effectively.

--

As previously highlighted, what you cannot do is automatically derive the type of f2 from the types used in f1 and

number</code. TypeScript lacks mechanisms to signify substituting types for type parameters directly. Neither <code>f1<number>
nor typeof f1<number> are valid expressions. TypeScript doesn't offer extensive support for higher kinded types at the pure type level to accomplish this functionality.

In TypeScript 3.4, enhanced backing was introduced for inferring generic functions through other generic functions. While direct manipulations like the ones you mentioned aren't possible solely at the type level, you can create functions that execute these alterations seamlessly. For instance:

function specify<A extends any[], R>(f: (...a: A) => R) {
    return () => f;
}

The specify() function accepts any function, even a generic one, and returns a new zero-argument function that mirrors the initial function if it was generic. Upon invoking, this returned function reproduces the primary function. This enables the following usage:

const f3 = specify(f1)<number>(); // const f3: (x: number) => number

specify(f1) yields a function of type <T>() => (x: T) => T. Hence, specify(f1)<number>() generates the non-generic (x: number)=>number function. This setup should operate smoothly during runtime as well:

const sixNumberStillOkay = f3(n); // okay
const sixStringStillNotOkay = f3(s); // error! string is not a number

If you find yourself frequently broadening generic functions into specific ones, then using specify() could prove beneficial, depending on your requirements.


I trust this information is helpful to you. Best of luck!

Link to code

Answer №2

In the snippet below, you can see how to define a generic type:

type Fn<T = any> = (x: T) => void

const f1: Fn = (x) => console.log(x)
const f2: Fn<number> = f1
f2('6') // Argument of type '"6"' is not assignable to parameter of type 'number'.

You can check out this working example 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

The imported path is not found in Tsconfig

Hey there! I've been working on getting my project's imports to play nice with typescript import paths. Every time I encounter this error : Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'app' imported from dist/index.js It seems l ...

Using React hooks with Material-UI: Snackbar displaying only on first occasion and not again

I have identified an issue that can be easily reproduced. Steps to replicate: Step 1: Start a react app and include material-ui in the project: prompt> create-react-app mui-test prompt> cd mui-test prompt> yarn add @material-ui/core @material-ui ...

Substitute Customized Interface Type Identifier

I am working on creating a versatile function interface for functor map while respecting the provided interface. In the code snippet below, my goal is to ensure that the value of mb is of type Maybe<number>, rather than the actual type Functor<num ...

Is app.component.ts necessary in an Angular 2 project?

Currently diving into Angular 2 and have a burning question on my mind. Do I really need the app.component.ts file in my project? Each of my folders has its own component and template, so I'm debating if the main component is necessary or if I can rem ...

The subsequent code still running even with the implementation of async/await

I'm currently facing an issue with a function that needs to resolve a promise before moving on to the next lines of code. Here is what I expect: START promise resolved line1 line2 line3 etc ... However, the problem I'm encountering is that all t ...

Array - Modifications do not pass down to the child component

I am observing the following structure in the code: <div id="join-container"> <join-chain id="my-join-chain" [selectedColumn]="selectedColumn" (updatedStatements)=onUpdatedStatements($event)> </join-chain> <tile-ca ...

Is there a way to execute tagged Feature/Scenario/Examples in Webdriverio-cucumber/boilerplate?

Hey there! I could use some assistance. I'm attempting to execute a specific scenario using Cucumber tags with the expression below: npx wdio run wdio.conf.js --cucumberOpts.tagExpression='@sanity and @stage' However, when I run the comman ...

Merging all Angular 2 project files into a single app.js document

I've scoured the depths of the internet for an answer to this burning question: How can I merge all my Angular 2 code, along with its dependencies, into a single file? Although this query has been posed countless times before, I bring a fresh perspect ...

"Implementing autocomplete feature with initial data in Angular 4 using FormControl

I have incorporated material.angular.io components into my app, particularly autocomplete. I am customizing it to function as a multi-select, but I am encountering an issue with loading initial values: export class CaseActivityTimeEditComponent implements ...

How can we access child components in vanilla JavaScript without using ng2's @ViewChild or @ContentChild decorators?

Recently, I delved into the world of using ViewChildren and ContentChildren in Angular 2. It got me thinking - can these be implemented in ES6 without TypeScript annotations? The TypeScript syntax, according to the official documentation, looks something ...

Convert a fresh Date() to the format: E MMM dd yyyy HH:mm:ss 'GMT'z

The date and time on my website is currently being shown using "new date()". Currently, it appears as: Thu May 17 2018 18:52:26 GMT+0530 (India Standard Time) I would like it to be displayed as: Thu May 17 2018 18:43:42 GMTIST ...

AngularTS regex that enforces the use of a decimal point in numbers

I am working on a requirement where the input should only accept decimal characters, negative or positive. I need to use regex to make the decimal point mandatory, however it is currently allowing negative whole numbers which is not the desired behavior. I ...

What is the best way to retrieve a variable that has been exported from a page and access it in _

Suppose this is my pages/visitor.tsx const PageQuery = 'my query'; const Visitor = () => { return <div>Hello, World!</div>; }; export default Visitor; How can I retrieve PageQuery in _app.tsx? One approach seems to be by assi ...

Transforming Uint8Array into BigInt using Javascript

I've come across 3 different ways to convert a Uint8Array to BigInt, but each method seems to produce varying results. Can someone clarify which approach is correct and recommended? Utilizing the bigint-conversion library. The function bigintConversi ...

Why am I receiving a peculiar type error with @types/jsonwebtoken version 7.2.1?

My current setup includes node v6.10.3, typescript v2.3.4, and jsonwebtoken v7.4.1. Everything was running smoothly until I decided to upgrade from @types/jsonwebtoken v7.2.0 to @types/jsonwebtoken v7.2.1. However, after this update, an error started poppi ...

Creating a unique user interface for VSCode extension

Recently, I've delved into the world of developing extensions for Visual Studio. Unfortunately, my expertise in TypeScript and Visual Studio Code is quite limited. My goal is to create an extension that mirrors the functionality of activate-power-mod ...

What is the process for launching a TypeScript VS Code extension from a locally cloned Git repository?

Recently, I made a helpful change by modifying the JavaScript of a VSCode extension that was installed in .vscode/extensions. Following this, I decided to fork and clone the git repo with the intention of creating a pull request. To my surprise, I discove ...

Error: zsh is unable to locate the command, even after defining it in package.json bin and installing it globally

I attempted to create a command-line application using TypeScript. Below is the code I have written: //package.json { "name": "jihea-cli", "version": "1.0.0", "description": "", "main": "index.ts", "bin": { "cli": "./bin/index.ts" }, // ...

Issue encountered with Typescript and Request-Promise: Attempting to call a type that does not have a call signature available

I have a server endpoint where I want to handle the result of an asynchronous request or a promise rejection by using Promise.reject('error message'). However, when I include Promise.reject in the function instead of just returning the async requ ...

Change the German number format from (0,01) to the English number format (0.01) using Angular 8

My application supports multiple languages. A user has selected German as their preferred language and I have registered it using registerLocale. I am able to convert decimal values from 0.001 (in English format) to 0,001 (in German format). However, when ...