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

A simple try for HKT #40368

Closed
wants to merge 41 commits into from
Closed

A simple try for HKT #40368

wants to merge 41 commits into from

Conversation

ShuiRuTian
Copy link
Contributor

@ShuiRuTian ShuiRuTian commented Sep 3, 2020

Fixes #1213

Far from complemention, just a try for now.


Two list, one is added ability, one is added error check.

Ability

  • class/interface accept type constructor as type parameter and map them correctly.
  • function accept type constructor as type parameter and map them correctly.
  • extends specific type constructor
  • type constructor could accept and check constraint
// constraint might be a very complex issue.
interface G1<T extends number>{}
interface G2<T extends string>{}
interface H1<Container<_> extends G1<_>>{}    // Container<string> should report error?
interface H2<Container<_ extends number>>{}
interface H3<Container<_> extends G1<_>|G2<_> >{}
interface H4<Container<_ extends number> extends G1<_ extends boolean>|G2<_ extends boolean> >{}

// Is H4 complex enough? 
// .......No, it just begin. I could not even give a constraint.
// Accorfing to the BFN, this is allowed.
interface H5<K1<K2<K3<_>>>>{}
// Could someone tell me what the hell is this? How could it be used in Scala or Haskell? And how to add constraint fot it?
  • default typeConstructor parameter
  • distinguish type constructor and proper type in quick info.
  • fix services
    • quick info
    • auto complete

// from communicity

Error

  • type parameters and type arguments must match for type constructor
  • infer type must match, this is an example in scala.
object HelloWorld {
   def main(args: Array[String]) {
      var q: List[Int] = List(1, 2, 3, 4)
      var w: Array[Boolean] = Array(true)
      var t = getData(q,q)
     
   }
   def getData[T<:Int,U<:Int,C[X<:Int]<:List[_]](a:C[T],b:C[U]) = 1/2
  
   def getData2[T,U,C[X],B[Q[_]]](a:C[T],b:C[U],c:B[C]) = 1/2
}

Here are some simple thoughts about constraints.

Most from scala.

Convert Type Constructor Instance to Type Lambda
List = [T] =>> List[T]

Convert apply constraint to Type Lambda
type T[X] >: L <: U convert to type T >: ([X] =>> L) <: ([X] =>> U)

Define <: on TypeLambda:
type TL1 = [X >: L1 <: U1] =>> R1
type TL2 = [X >: L2 <: U2] =>> R2
TL1 <: TL2 if

  • the type interval L2..U2 is contained in the type interval L1..U1 (i.e. L1 <: L2 and U2 <: U1) and
  • R1 <: R2
    The conditions are reasonable: assume we have interface F[G[T<:U1] <: G1[T]], G[Timpl], so Timpl <: U1,
    then we replace G[T] with Gimpl[U<:U2] <: G2[U], if the U is stricter(U2<:U1), Timpl might not meet Timpl <: U2;
    And we use functions in G1[T], if G2[U] <: G1[T] these functions might not exist.

If A <: B
A and B both have met the contraints.
TypeLambda[A] = TypeLambda[B] is valid if TypeLambda[+T];
TypeLambda[B] = TypeLambda[A] is valid if TypeLambda[-T];

@typescript-bot typescript-bot added the For Backlog Bug PRs that fix a backlog bug label Sep 3, 2020
@Kingwl
Copy link
Contributor

Kingwl commented Sep 14, 2020

Cool!

Could you fix the test cases(or lint or merge master) and we could pack this.

@ShuiRuTian
Copy link
Contributor Author

Alright, finally......
It is a little harder than estimated, however, it passed........

@Kingwl
Copy link
Contributor

Kingwl commented Sep 16, 2020

seems not work again....

@ShuiRuTian
Copy link
Contributor Author

Oh, I see the cute little green check mark and would not push any more until it is packed this time

src/compiler/types.ts Outdated Show resolved Hide resolved
@orta
Copy link
Contributor

orta commented Sep 22, 2020

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 22, 2020

Heya @orta, I've started to run the tarball bundle task on this PR at 9d1850c. You can monitor the build here.

@Kingwl
Copy link
Contributor

Kingwl commented Dec 31, 2020

@typescript-bot pack this.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 31, 2020

Heya @Kingwl, I've started to run the tarball bundle task on this PR at 8842bad. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 31, 2020

Hey @Kingwl, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/92305/artifacts?artifactName=tgz&fileId=9E5FDE7C19BFACFB087CB96E02650F8E6527658807B020A62C0AD8F441C2B7D702&fileName=/typescript-4.1.0-insiders.20201231.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/pr-build@4.1.0-pr-40368-20".;

@jeremiahrhall
Copy link

I've tried this out locally and it's really interesting. The tests pass and I'm able to use it in VSCode. Is there anything I can help with here? Testing?

@ShuiRuTian
Copy link
Contributor Author

@jeremiahrhall Thanks a lot for the kind willing!

I have almost forgot this PR..... for many reasons:

  1. Not pretty clear about the syntax and concept. Although this PR could basically do the work asked for in the issue, but it is only the most simple case. Have no many thoughts about the advanced situation:

    1. What is "higher" kind type. As I have seen, when people talking about HKT, in most cases, it is actually "two kind type". Then issues come, what is the use of more higher kind type, should we support them, if yes what is its syntax and error...
    2. A big problem not mentioned is the constraint. As you could see in my description, how to add constraint is not that clear and might be a very difficult thing, especially if we want to support three and more kind type. let me explain the difficult a bit:
      • data struct. Could current data struct support constraint? If not, We might need change ten thousands of lines.
      • compatibility. Other functions provided by TS, like infer keyword, how to be compitated with HKT?
  2. It would be a little awkward to mention, but some personal stuff block me, I hope all things would go well so that I could have more free time.

  3. I have some other PRs ready for reivew, as far as I'm concerned, I would like to see them merged first, then focusing on the most hard and interesting one.

And, yes, of course, testing this would be really helpful, especially when it could fit all your need. I am curious whether it is a common partten without needing constraint. If so, perhaps we could argue a bit with TS Team!

@2A5F
Copy link

2A5F commented Mar 20, 2021

@ShuiRuTian
for what is "higher", constraint, infer, this can be as a reference
#35816

@ShuiRuTian
Copy link
Contributor Author

ShuiRuTian commented Mar 22, 2021

@2A5F

Thanks for the kind willing.

Some parts of the link looks good. However, other parts are basic and lack details. It would be better if you could give more detailed design.

  1. higher kinded type
    *->*, this is generic, one kind.
    (*->*)->*, this is two kind, and in most cases, this is the form when people give examples.
    ((*->*)->*)->*, this is three kind, and it is also HKT.
    So, HKT syntax should be able to express about TEN kind and more, although this might be ridiculous.

  2. constraint
    And this part, an important question is whether one constraint meet another constraint.

interface MyGeneric<T extends string>{}
const foo: MyGeneric<number>; // error, number is not string

And in HKT, this is much more complex

interface MyGeneric1<T extends string>{}
interface MyGeneric2<T extends number>{}
interface MyFunctor<G<T extends string> extends MyGeneric1<T>>{}
const foo: MyFunctor<MyGeneric2>; // error, number is not string, and MyGeneric2 is not MyGeneric1
// Think about three kinded HKT, it would be complex.
// Look this:
// interface HKT3<Functor<Generic1<T extends number> extends Generic2<T>, Generic2<U exnteds V> exnteds Set<V>>, V extends string>
// Is HKT3 valid?
// If we have HKT3<FunctorImpl, string>, should this be valid?

The conclution is type parameter should be cotravariance, and return type should be covariance.

  1. infer

I do not have many idea about this, but it must be difficult.

And here is one question I am not sure:
(*->*)->(*->*), this is indeed HKT, but in which condition, could we return a Type Constructor?

@2A5F
Copy link

2A5F commented Mar 22, 2021

@ShuiRuTian

HKT3 certainly cannot be valid

type Foo<V extends string, U extends V> = U
type Bar<V extends string, T extends number> = Foo<V, T>

// Type 'T' does not satisfy the constraint 'V'.
//   'V' could be instantiated with an arbitrary type which could be unrelated to 'T'.
//     Type 'number' is not assignable to type 'V'.
//       'V' could be instantiated with an arbitrary type which could be unrelated to 'number'.

V is passed in like a closure

Playground


interface MyGeneric1<T extends string>{}
interface MyGeneric2<T extends number>{}
interface MyFunctor<
  G extends for<T extends string> MyGeneric1<T>
>{}
const foo: MyFunctor<MyGeneric2>;

I think my syntax is more readable and easier to think
for<T extends string> MyGeneric1<T>
just like a function or type kind syntax
(T: string) => MyGeneric1<T> , * -> *


interface HKT3<
  Functor extends for<
     Generic1 extends for<T extends number> Generic2<T>, // T error
     Generic2 exnteds for<U exnteds V> Set<V>
  >, 
  V extends string
>

How to write FunctorImpl ?

interface FunctorImpl<
  Generic1 extends for<T extends number> Generic2<T>, // T error
  Generic2 exnteds for<U exnteds string> Set<string>
> { }

// or

type G2<U exnteds string>= Set<string>
type G1<T extends number> = G2<T> // T error

interface FunctorImpl<
  Generic1 extends G2,
  Generic2  extends G1,
>{ }

for<U exnteds string> Set<string> <- for<U extends string | number> Set<'a'>
// just like
(u: string) => Set<string> <- (u: string | number) => Set<'a'>

In other words, the type constructor is contravariant in the parameter (input) type and covariant in the return (output) type.

wiki


but in which condition, could we return a Type Constructor?

If a type constructor is not applied
or use the type constructor literal

there is no partial application

type Foo<T> = T
type Bar = Foo // Bar is type Bar<T> = T

type Foo<T> = for<U> T | U
type Bar = Foo<1> // Bar is type Bar<T> = 1 | T

@sandersn
Copy link
Member

This experiment is pretty old, so I'm going to close it to reduce the number of open PRs.

@sandersn sandersn closed this May 24, 2022
@rokinsky
Copy link

rokinsky commented Jun 29, 2022

@sandersn, your development lead approved the approach and asked for a draft implementation — #1213 (comment).

The case with one type parameter is basically what we need in 95%, e.g.

interface Functor<F<_>> {
  map<A, B>(f: (a: A) => B): F<A> => F<B>;
}

I hope you have closed this PR to continue working on the implementation within the Microsoft team, as the HKT topic is enormously in demand — you can look at the related discussion.

For a reference, you can take an advanced Scala compiler and their HKT implementation.

@aliakbarazizi
Copy link

@sandersn @lupuszr any update on this?
this pull request exist for more than 3 years and related issue is open for almost 9 years
#1213

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Experiment A fork with an experimental idea which might not make it into master For Backlog Bug PRs that fix a backlog bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow classes to be parametric in other parametric classes