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

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 ...

Webpack is throwing an error due to the Vue component type being labeled as "any"

When using ts 4.2.4 and Vue3, I encountered a strange error while building my vue project: > <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c3a2a7aeaaededb3a2a0a083f3edf2edf3">[email protected]</a> build > v ...

Why is my Typescript event preventDefault function ineffective?

Despite all my efforts, I am still unable to prevent the following a tag from refreshing the page every time it's clicked. <p> <a onClick={(e) => handleClick} href="&qu ...

Determining the type inference in Typescript based on column data objects

Within my object that describes my table, I have a property called dataFields, which is an array of data with 3 keys - name (a required string), label (a required string), and field (an optional string). Now, I would like to add another property called tes ...

Ways to prevent encountering the "ERROR: Spec method lacks expectations" message despite achieving success

My Angular HTTP service is responsible for making various HTTP calls. Below is a snippet of the service implementation: import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable() expor ...

The Ionic search bar will only initiate a search once the keyboard is no longer in view

In my Ionic application, I have implemented a search bar to filter and search through a list. The filtering process is triggered as soon as I start typing in the search bar. However, the updated results are not displayed on the screen until I manually hide ...

Converting text data into JSON format using JavaScript

When working with my application, I am loading text data from a text file: The contents of this txt file are as follows: console.log(myData): ### Comment 1 ## Comment two dataone=1 datatwo=2 ## Comment N dataThree=3 I am looking to convert this data to ...

Is it possible to nullify an object and utilize nullish coalescing for handling potentially undefined constants?

In my development work with React, I often utilize a props object structured like this: const props: { id: number, name?: string} = { id: 1 }; // 'name' property not defined const { id, name } = props; // the 'name' constant is now fore ...

Implementing theme in Monaco editor without initializing an instance

I recently developed a web application incorporating Monaco Editor. To enhance user experience, I also integrated Monaco for syntax highlighting in static code blocks. Following guidance from this source, I successfully implemented syntax highlighting wit ...

The Nest.js Inject decorator is not compatible with property-based injection

I am facing an issue with injecting a dependency into an exception filter. Here is the dependency in question: @Injectable() export class CustomService { constructor() {} async performAction() { console.log('Custom service action executed ...

What steps are involved in constructing Jodit from scratch?

Seeking a non-minified, readable version of Jodit, I attempted to build it myself. However, my lack of experience with composer, node, npm, webpack, TypeScript, and other tools has left me struggling. Is there anyone who can guide me through the process s ...

Karma Jasmin is puzzled as to why her tests are failing intermittently, despite the absence of any actual test cases

In this snippet, you will find my oninit method which I am instructed not to modify. ngOnInit(): void { this.setCustomizedValues(); this.sub = PubSub.subscribe('highlightEntity', (subId, entityIdentifier: string) => { ...

There is a potential for the object to be 'undefined' when calling the getItem method on the window's local storage

if (window?.sessionStorage?.getItem('accessToken')?.length > 0) { this.navigateToApplication(); } Encountering the following error: Object is possibly 'undefined'.ts(2532) Any suggestions on how to resolve this issue? I am attem ...

What is preventing type-graphql from automatically determining the string type of a class property?

I have a custom class named Foo with a property called bar that is of type string. class Foo { bar: string } When I use an Arg (from the library type-graphql) without explicitly specifying the type and set the argument type to string, everything works ...

`When attempting to use Typescript with Electron, the error 'exports is not defined

Trying to launch my basic electron application, I utilize Typescript for development which then compiles into JavaScript. However, upon running the app, an error is encountered: ReferenceError: exports is not defined[Learn More] file:///Users/ahmet/Docume ...

What is the best way to implement lazy loading for child components in React's Next.js?

I am exploring the concept of lazy loading for children components in React Next JS. Below is a snippet from my layout.tsx file in Next JS: import {lazy, Suspense} from "react"; import "./globals.css"; import type { Metadata } from &quo ...

The Angular 2 view will remain unchanged until the user interacts with a different input box

I am currently working on implementing form validation using Reactive Forms in Angular 2. Here is the scenario: There are two input fields Here are image examples for step 1 and step 2: https://i.stack.imgur.com/nZlkk.png https://i.stack.imgur.com/jNIFj ...

Does the routing in Angular 2 get disrupted by parameter breaks in sub-modules?

In my Angular 2 application, I am encountering an issue with routing to my line module. Currently, I have two submodules - login and line. The routing to the login submodule is working well. However, when I attempt to route to the line module with route pa ...

Is there a way to modify the style within a TS-File?

I've created a service to define different colors and now I want to set separate backgrounds for my columns. However, using the <th> tag doesn't work because both columns immediately get the same color. Here's my code: color-variatio ...

Is it possible to import a class from a different project or module in TypeScript?

I am attempting to modify the build task in Typescript within this specific project: https://github.com/Microsoft/app-store-vsts-extension/blob/master/Tasks/app-store-promote/app-store-promote.ts I am looking to incorporate an import similar to the one be ...