Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First class type constructor #35816

Open
5 tasks done
2A5F opened this issue Dec 21, 2019 · 3 comments
Open
5 tasks done

First class type constructor #35816

2A5F opened this issue Dec 21, 2019 · 3 comments
Labels
Needs More Info The issue still hasn't been fully clarified

Comments

@2A5F
Copy link

2A5F commented Dec 21, 2019

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

@2A5F 2A5F changed the title Fitst class type First class type Dec 21, 2019
@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label Jan 13, 2020
@RyanCavanaugh
Copy link
Member

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

@2A5F 2A5F changed the title First class type First class type constructor Dec 30, 2020
@2A5F 2A5F closed this as completed Jan 4, 2021
@2A5F 2A5F reopened this Feb 7, 2021
@2A5F 2A5F mentioned this issue Mar 20, 2021
12 tasks
@2A5F
Copy link
Author

2A5F commented Sep 16, 2021

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
Copy link

This is exactly what I'm looking for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs More Info The issue still hasn't been fully clarified
Projects
None yet
Development

No branches or pull requests

3 participants