microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.76k stars 12.46k forks source link

First class type constructor #35816

Closed 2A5F closed 2 months ago

2A5F commented 4 years ago

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

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

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:

Reference

1213

https://github.com/microsoft/TypeScript/issues/1213#issuecomment-370763683

5959

29904

30215

31116

41040

44875

48036

48820

first-class-protocols

RyanCavanaugh commented 4 years ago

What's the differentiation between this and #1213 or other HKT issues?

2A5F commented 3 years ago

Follow-up proposal

Type Extension

type <T>.Foo = [T]
type foo = 1.Foo      // foo is [1]

type <A>.Bar<B> = [A, B]
type bar = 1.Bar<2>   // bar is [1, 2]
type <O>.Omit<K> = Omit<O, K>
type <O>.Keys= keyof O
type <O>.Vals = O[O.Keys]
type <O>.Map<F extends for<[O.Keys, O.Vals]>> = { [K in O.Keys]: F<K, O[K]> }

type OmitValue<O, T> = O.Omit<O.Map<for<K, V> V extends T ? K : never>.Vals>
// same to
type OmitValue<O, T> = Omit<O, { [K in keyof O]: O[K] extends T ? K : never }[keyof O]>
matthew-dean commented 9 months ago

This is exactly what I'm looking for.