What can TypeScript do with high-level type functions?

Take a look at the following pseudo-code attempting to define a higher-order type function with a function-typed parameter M<?>:

type HigherOrderTypeFn<T, M<?>> = T extends (...)
  ? M<T>
  : never;

The syntax M<?> is not valid TypeScript, but defining the type as HigherOrderTypeFn<T, M> triggers an error

Type 'M' is not generic. ts(2315)
on the second line.

Does this mean that such a type is currently impossible to represent in TS?

Answer №1

It is true that TypeScript currently does not support higher kinded types. A feature request on GitHub, microsoft/TypeScript#1213, discusses this issue under the title "Allow classes to be parametric in other parametric classes".

While there are suggestions on how to simulate higher kinded types within the language, it may not be suitable for production code. If you have a specific structure in mind, feel free to share and seek advice.

If you believe in pushing for this feature, consider showing support on the GitHub issue by giving it a thumbs up or explaining your use case to help make it more compelling. Every little bit helps!

Answer №2

If you're looking for a workaround, here's a simple suggestion that uses placeholders as a basis (refer to this comment in the discussion highlighted by jcalz):

type Placeholder = {'aUniqueKey': unknown};
type Replace<T, X, Y> = {
  [k in keyof T]: T[k] extends X ? Y : T[k];
};

Therefore, your function would appear like this:

type HigherOrderTypeFn<T, M> = T extends (...) ? Replace<M, Placeholder, T> : never;

and can be invoked, for instance, in this way:

type M<U> = U[];
type X = HigherOrderTypeFn<number, M<Placeholder>> // results in number[] (if ... is number)

Answer №3

For those who stumble upon this information, there is a great example circulating on the TypeScript discord community:

export interface Hkt<I = unknown, O = unknown> {
  [Hkt.isHkt]: never,
  [Hkt.input]: I,
  [Hkt.output]: O,
}

export declare namespace Hkt {
  const isHkt: unique symbol
  const input: unique symbol
  const output: unique symbol

  type Input<T extends Hkt<any, any>> =
    T[typeof Hkt.input]

  type Output<T extends Hkt<any, any>, I extends Input<T>> =
    (T & { [input]: I })[typeof output]

  interface Compose<O, A extends Hkt<any, O>, B extends Hkt<any, Input<A>>> extends Hkt<Input<B>, O>{
    [output]: Output<A, Output<B, Input<this>>>,
  }

  interface Constant<T, I = unknown> extends Hkt<I, T> {}
}

There are various use cases for this example code. One key component is defining a SetFactory, allowing you to specify the desired set type when creating a factory, such as typeof FooSet or typeof BarSet. The constructor type takes any T and returns a FooSet<T>, similar to a higher kinded type. Methods like createNumberSet can then be used to create new sets of a specific type, with parameters set accordingly.

interface FooSetHkt extends Hkt<unknown, FooSet<any>> {
    [Hkt.output]: FooSet<Hkt.Input<this>>
}
class FooSet<T> extends Set<T> {
    foo() {} 
    static hkt: FooSetHkt;
}

interface BarSetHkt extends Hkt<unknown, BarSet<any>> {
    [Hkt.output]: BarSet<Hkt.Input<this>>;
}
class BarSet<T> extends Set<T> { 
    bar() {} 
    static hkt: BarSetHkt;
}

class SetFactory<Cons extends {
    new <T>(): Hkt.Output<Cons["hkt"], T>;
    hkt: Hkt<unknown, Set<any>>;
}> {
    constructor(private Ctr: Cons) {}
    createNumberSet() { return new this.Ctr<number>(); }
    createStringSet() { return new this.Ctr<string>(); }
}

// SetFactory<typeof FooSet>
const fooFactory = new SetFactory(FooSet);
// SetFactory<typeof BarSet>
const barFactory = new SetFactory(BarSet);

// FooSet<number>
fooFactory.createNumberSet();
// FooSet<string>
fooFactory.createStringSet();

// BarSet<number>
barFactory.createNumberSet();
// BarSet<string>
barFactory.createStringSet();

To better understand how this works, let's consider the interaction between FooSet and number:

  • The crucial point is the type
    Hkt.Output<Const["hkt"], T>
    . In our case, this translates into
    Hkt.Output<(typeof FooSet)["hkt"], number>
    , which ultimately results in FooSet<number>.
  • We first resolve (typeof FooSet)["hkt"] to FooSetHkt, storing the creation details in the static hkt property of FooSet. This step should be repeated for each supported class.
  • Next, we have
    Hkt.Output<FooSetHkt, number>
    . By resolving the Hkt.Output alias, we get
    (FooSetHkt & { [Hkt.input]: number })[typeof Hkt.output]
    , utilizing unique symbols to ensure uniqueness.
  • Now, accessing the Hkt.output property of FooSetHkt, we find the instructions to construct a concrete type with the argument. For FooSetHkt, the output is defined as
    FooSet<Hkt.Input<this>>
    .
  • Finally, through Hkt.Input<this>, we access the Hkt.input property of FooSetHkt. By tweaking it to include number, we reach number as the result, leading to FooSet<number>.

In essence, the previous discussion surrounding Hkt.Output applies to the given example but with reversed type parameters:

interface List<T> {}
interface ListHkt extends Hkt<unknown, List<any>> {
    [Hkt.output]: List<Hkt.Input<this>>
}
type HigherOrderTypeFn<T, M extends Hkt> = Hkt.Output<M, T>;
// Outputs List<number>
type X = HigherOrderTypeFn<number, ListHkt>;

Answer №4

There is an innovative use of Higher Kinded Types (HKTs) by taking advantage of module augmentation in fp-ts library.

To navigate around the limitations of Higher Kinded Types, the author has provided a solution through this documentation:

export interface HKT<URI, A> {
  readonly _URI: URI;
  readonly _A: A;
}

This can be implemented as follows:

export interface Foldable<F> {
  readonly URI: F;
  reduce: <A, B>(fa: HKT<F, A>, b: B, f: (b: B, a: A) => B) => B;
}

For further clarification on this topic, consider visiting this question: Higher Kinded Type in TypeScript from fp-ts and URI

Exploring this discussion may illuminate some aspects for you.

Cheers!

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

`Error importing react-markdown in Next.js 11.1 with TypeScript``

Having trouble with importing react-markdown in my next.js SSG project. When running npm run dev, I encounter an error that prevents me from proceeding to test in next export. I understand that react-markdown is an esm package, but I'm not sure how t ...

How can one access DOM elements (getting and setting values) that are nested within an *ngFor loop?

How can I access the <span> and <select> elements in my code template shown below? <div *ngFor="---"> <div> <span></span> <select> <option></option> <option></option> ...

A comprehensive guide on displaying data in Angular using an API

I have encountered an issue while trying to display data from an API in the 'home.component.html'. Although my 'home.component.ts' successfully fetches the data from the service, I'm facing difficulty rendering it in 'home.com ...

Enhance your workflow with Visual Studio Code by incorporating multiple commands

Embarking on my journey to create my first VSC extension by following this tutorial. Within the "extension.ts" file resides a simple hello world command. My ambition is to introduce another command called git_open_modified_files, however, the tutorial la ...

Jest test encounters an error due to an unexpected token, looking for a semicolon

I've been working on a Node project that utilizes Typescript and Jest. Here's the current project structure I have: https://i.stack.imgur.com/TFgdQ.png Along with this tsconfig.json file "compilerOptions": { "target": "ES2017", "modu ...

increase the selected date in an Angular datepicker by 10 days

I have a datepicker value in the following format: `Fri Mar 01 2021 00:00:00 GMT+0530 (India Standard Time)` My goal is to add 60 days to this date. After performing the addition, the updated value appears as: `Fri Apr 29 2021 00:00:00 GMT+0530 (India St ...

When evaluating objects or arrays of objects to determine modifications

How can we detect changes in table data when users add input to cells? For example, if a user clicks on a cell and adds an input, the function should return TRUE to indicate that there are changes. If the user just clicks on the cell without making any ch ...

Tips for effectively typing a collection of React wrappers in TypeScript

I encountered a situation in my team's application where we need the ability to dynamically compose component wrappers (HOCs) without prior knowledge of all the wrapper interfaces. This is mostly needed for swapping out context providers when renderin ...

Currently focused on developing vertical sliders that can be manipulated by dragging them up or down independently

https://i.stack.imgur.com/NgOKs.jpg# I am currently working on vertical sliders that require dragging up and down individually. However, when I pull on the first slider, all sliders move together. The resetAllSliders button should also work independently, ...

TS - deduce the specific type of a key value without receiving a union type

Welcome to the coding playground: Click here to start coding Let's talk about a scenario where a function is expected to return some value based on an input argument. The challenge arises when there are keys with the same name but different types re ...

Clicking on an element- how can I find the nearest element?

Encountering an issue with my next js app where I am attempting to assign a class to an element upon clicking a button. The problem arises when trying to access the next div using the following code snippet: console.log(e.target.closest('.request_quot ...

Is it possible to utilize an XML format for translation files instead of JSON in React Native?

I'm in the process of creating a react native application using the react i18next library. For translations, I've utilized XML format in android for native development. In react native, is it possible to use XML format for translation files inste ...

Exploring the Depths of JSON Arrays within Typescript

I am faced with a challenge in extracting the value of the "id" from the following array of JSON data. The issue lies in the fact that the value is enclosed within double square brackets "[[" which are causing complications in retrieving the desired result ...

Identifying unique properties with specific keys in a Typescript object

Can a specific type be used with one property when using the key in of type? Playground. type ManyProps = 'name' | 'age' | 'height' type MyObj = {[key in ManyProps]: number, name?: string} ...

Developing an Angular 2 Cordova plugin

Currently, I am in the process of developing a Cordova plugin for Ionic 2. The plugin is supposed to retrieve data from an Android device and display it either on the console or as an alert. However, I am facing difficulty in displaying this data on the HT ...

What is the best way to set a checkbox to null instead of false using Angular?

I am currently developing a filtering system that allows users to select different parameters to filter through a list of items. For instance, the item list could be a collection of dishes with filters represented as checkboxes labeled as vegan, vegetaria ...

Executing a Prisma query with a selection

My Prisma models involve User, Car, and Reservation entities: model User { id String @id @default(auto()) @map("_id") @db.ObjectId name String? email String? @unique emailVerified DateTime? image ...

Creating an auth guard in Angular Fire v7 using the latest API (not backwards compatible)

I encountered an issue Error: Unable to handle unknown Observable type when attempting to utilize v7 Angular Fire with the latest API. Specifically "@angular/fire": "^7.4.1" which corresponds to angular 14, firebase 9. Please not ...

Is it necessary to create a unit test for a basic operation involving RxJS?

Imagine a straightforward class that triggers a new event to an RxJS subject whenever the window is resized. Disregard any perceived complexities, as the main point is that this class generates an event. export class ResizeService { priv ...

Create an array of arrays within a loop using TypeScript

My application contains an object with dates and corresponding time arrays. The console log output is displayed below: 32: { 1514160000: Array [ 1200, 1500 ], 1514764800: Array [ 1200, 1500 ], 1515369600: Array [ 1200, 1500 ], 1515974400: Array [ 700, 12 ...