Skip to content

First class type constructor  #35816

Closed as not planned
Closed as not planned

Description

Search Terms

first class type alias constructor hkt variadic generics transitivity independent independence first-class first-class-type first-class-type-alias first-class-type-constructor variadic-generics higher-kinded-types hrt higher kinded rank types rank-n

Suggestion

Add first class type constructor support

This proposal contains the following features

  • HKT (Higher Kinded Types)
  • Variadic Generics
  • Independent type constructor
  • Transitivity of type constructor

HKT

interface Functor<F extends type<T>> {
    map<A, B>(f: (a: A) => B, s: F<A>): F<B>
}

The following uses this 'type' ('<' <args>,+ '>')? <return>? syntax
although this syntax has compatibility issues

This is already very useful, but there are many other problems

Transitivity & Independence

declare function foo<T>(v: T): T
type params = Parameters<typeof foo>    // type params = [v: unknown]
type rets = ReturnType<typeof foo>      // type rets = unknown

Playground

Because it is not transitive, generics are discarded during inference,
so we need the type constructor to be transitive,
and this requires that the type constructor must be independent

with transitivity and independence, this will be

declare function foo<T>(v: T): T
type params = Parameters<typeof foo>    // type params = type<T> [v: T]
type rets = ReturnType<typeof foo>      // type rets = type<T> T

Independent type constructor should not exist at runtime,
so when an independent type constructor is used as a type without being applied,
the generic type should be discarded again

let a: ReturnType<typeof foo><1>    // let a: 1
let b: Parameters<typeof foo><1>    // let b: [v: 1]

let c: ReturnType<typeof foo>       // let c: unknown
let d: Parameters<typeof foo>       // let d: [v: unknown]

The transitivity of type constructors should also exist on type aliases

type params = type<T> [v: T]    // type params<T> = [v: T]
type rets = type<T> T           // type rets<T> = T

*
This type syntax is very loose,
You can represent not only type constructors with parameters,
but also type constructors without parameters,
or you can optionally fill in the return type in the type parameter

type num = type number          // type num = number
type Foo<T extends type<A, B> A | B> = T
// This means T extends type<A, B> => R where R extends A | B
// or like associated-types in rust | swift
// type<A, B> {
//  type R: A | B
// }

type Bar<T extends type<A>> = T
// For external T extends type<A> => unknown
// For internal T extends type<A> => anonymous nominal unique type

Variadic Generics

The current improvised solution is

type Foo<T extends any[]> = T

Normally this is enough, but it is not free enough when the type constructor is transitive
if has variadic generics, then the type constructor can be inferred

  • Variadic syntax

    type Foo<...T extends any[]> = T
    
    type Bar = Foo<1, 2, 3>   // type Bar = [1, 2, 3]
  • Infer TypeReturnType

    type TypeReturnType<T extends type<...A extends any[]> any> =
        T extends type<...A extends any[]> infer R ? R : never
    
    interface Foo<T> { a: T }
    type Bar<A, B> = A | B
    
    type use = TypeReturnType<Foo>    // return type<T> { a: T }  or return Foo
    type use = TypeReturnType<Bar>    // return type<A, B> A | B
  • Infer TypeParameters

    type TypeParameters<T extends type<...A extends any[]> any> =
        T extends type<...A extends infer P> any ? P : never
    
    type Foo<A, B> = any
    
    type use = TypeParameters<Foo>    // return type<A, B> [A, B]

Compatibility

It is a pity that although this type syntax is very readable, it has compatibility issues

type type = 1
type foo = type

Other possible syntax

type A = type: any
type A = type<B>: any

type A = type: any
type A = type:<B> any

type A = (type = any)
type A = (type<B> = any)

type A = ~any
type A = ~<B>any

type A = any
type A = <B>any

type A = for any
type A = for<B> any

What is First class Type constructor

* -> * is type constructor
(* -> *) -> * is HKT
* -> * -> * is binary type constructor with currying
(* -> *) -> (* -> *) is first class type constructor without currying but with transitivity, independent

Examples

interface Monad<T extends type<X>> {
  map<A, B>(f: (a: A) => B): T<A> => T<B>
  lift<A>(a: A): T<A>
  join<A>(tta: T<T<A>>): T<A>
}
function mixin<B extends type<...P extends any[]> new (...a: any[]) => any>(Base: B) {
  return class<...P extends TypeParameters<B>> extends Base<...P> { }
}
class A<T> { }
class B<T> extends mixin(A)<T> { }

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Reference

#1213
#1213 (comment)
#5959
#29904
#30215
#31116
#41040
#44875
#48036
#48820

first-class-protocols

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions