tc39 / proposal-type-annotations

ECMAScript proposal for type syntax that is erased - Stage 1
https://tc39.es/proposal-type-annotations/
4.26k stars 47 forks source link

An alternative syntax proposal using :: #84

Open jethrolarson opened 2 years ago

jethrolarson commented 2 years ago

So I'm not sure what the best way to engage here is but I put together an alternate syntax that more closely accomplishes the goal of types-as-comments while addressing some of the complaints I have about languages that mix types and terms in the same space. JSDoc doesn't do this, but TypeScript does. Both have advantages, and my proposal aims to get at them.

https://github.com/jethrolarson/proposal-types-as-comments

jethrolarson commented 2 years ago

I only updated the README, btw.

Nixinova commented 2 years ago

I really like that, seems much better than the complexity that this proposal in its current form would create. Easier to parse (just ignore the whole line) and keeps the actual JavaScript code clutter free.

giltayar commented 2 years ago

I like it! Very Haskell-ish, and is a subtle reference to the jsdoc typings.

But it is a goal of this spec to be mostly compatible with the massive amounts of Flow and TypeScript code out there. Alternative specs were just not considered because of this goal.

Nixinova commented 2 years ago

it is a goal of this spec to be mostly compatible with the massive amounts of Flow and TypeScript code out there.

Adding type hints without proper type checking seems conceptually incompatible with what TypeScript's syntax makes the code imply. Copying it verbatim would just make it so that actual type checking could never be added in the future as it'd clash, would it not?

giltayar commented 2 years ago

Copying it verbatim would just make it so that actual type checking could never be added in the future as it'd clash, would it not?

This has been mentioned multiple times. And, yes, as it stands, this proposal would not enable future type standardization and checking. But there are multiple way out of this problem if type checking would want to be added to the standard (e.g. a "use types" mode).

benjamingr commented 2 years ago

Honesty this looks significantly less readable than the current TypeScript/flow syntax

VitorLuizC commented 2 years ago

Similar to #80

jethrolarson commented 2 years ago

@VitorLuizC I don't think it has much in common with #80 other than it's another syntax proposal.

I suppose it's worth mentioning that it's inspired by the haskell-ish type annotations used by Ramda and other parts of the FP js community.

I have been considering making it actually "types as comments" as that is totally possible with my approach. Just change :: to //: or whatever.

I really think that TS and Flow used inline types not because it's better but because it's what all the other popular programming languages do. It's like putting semi-colons in languages because it "feels right". In trivial examples it makes little difference but if you do anything with HOFs and it becomes bewildering

Here's a motivating example for those that didn't scroll all the way down:

Comparison to the original proposal

This proposal aims to separate types and terms in a way that enables comfortable use of generics, currying, and higher-order functions in a way that languages struggle with. Here is an illustrative example from TypeScript.

const compose = <A, B, C>(g: (y: B) => C) => (f: (x: A) => B) => (a: A): C => g(f(a));

Even though this is a simple function that is known as compose operator or the B combinator, due to the verbosity and complection of types and terms it is visually hard to process. Following this proposal would make it look like:

::<A, B, C>(B -> C) -> (A -> B) -> A -> C
const compose = (g,f) => a => g(f(a))

This makes it easy to study the actual code or the type independently to understand them without getting types and terms mixed up in your head.

Want to take the type for a statement and turn it into a type alias? just whack type in-front of it.

simonbuchan commented 2 years ago

It may well be the case that it's better for more complex cases and that Typescript (and Actionscript and Flow etc) could have been better off if they picked that syntax.

But they didn't. They all picked the convention of interleaving the types that most every other language does, for whatever reason.

Remember, the only reason this is a proposal at all is the overwhelming popularity of typescript, (and the relatively trivial spec impact of "just ignore them")

I think for this (or #80) to be meaningful, you would have to convince a significant chunk of developers to start using the syntax in a transpiler first, as unlike most other proposals, this doesn't actually do anything, so it only has meaning when easing an existing workflow. Maybe that looks like Typescript adding this syntax and pushing people to it, like a nuclear version of the cast syntax change; but I just don't see it realistically happening.

I even mostly like it, a pretty high bar, if I do say so myself! But it has the issue of being line delimited in a (mostly) sigil delimited language.

kee-oth commented 2 years ago

I'm personally very much in favor of something like this. I believe it to be more readable in general (obviously not to those that already are familiar with TS) and it is much easier to use a type system like this for functional programming (which TypeScript really struggles with since it began life as type system focused on object-oriented code).

For those that don't know, the inspiration for the documentation style for Ramda, Sanctuary, and some other functional oriented JS libraries is called the Hindley-Milner type system (maybe worth mentioning in the original post, @jethrolarson ?).

I also really like separating the type from the actual JS code. I think it's particularly less overwhelming to new developers and it's easy to add: you just add a line above a function and you're good to go.

I'm aware that the goal of the proposal is to basically make TypeScript code run in browsers but I'm very concerned about this proposal overlooking the needs and wants of the functional JS community in favor of a large community that just wants their thing in the browser (what community wouldn't be in favor of that?).

https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system

giltayar commented 2 years ago

I also really like separating the type from the actual JS code. I think it's particularly less overwhelming to new developers and it's easy to add: you just add a line above a function and you're good to go.

I'm aware that the goal of the proposal is to basically make TypeScript code run in browsers

And Node! And Deno! People keep forgetting that... 😀 (off-topic, I know. Just a personal itch)

but I'm very concerned about this proposal overlooking the needs and wants of the functional JS community in favor of a large community that just wants their thing in the browser (what community wouldn't be in favor of that?).

It's a tradeoff. And in this tradeoff, I would prefer a proven and loved solution that a lot of the community is behind (TypeScript) than a new type system that's never been tried before and has to overcome the huge barrier to entry that is existing TS code.

Yes, I want the pure and perfect solution. But the best solution is not always the perfect one. This is the web, and the web started out as an imperfect solution. But it evolved, imperfectly. And that the end result of that evolution is most definitely not the pure and perfect solution. But it works. And it works well.

And, yes, I still do wish for a more functional/haskell-ish approach.

(Sorry, I'll stop being sentimental now ☺️)

benjamingr commented 2 years ago

For what it's worth this proposal does allow:

const compose:<A, B, C>(B -> C) -> (A -> B) -> A -> C
 = (g,f) => a => g(f(a))

To my understanding, you can still write a type checker on top of it that works like that

kee-oth commented 2 years ago

It's a tradeoff. And in this tradeoff, I would prefer a proven and loved solution that a lot of the community is behind (TypeScript) than a new type system that's never been tried before and has to overcome the huge barrier to entry that is existing TS code.

I think it's a bit dismissive to say overlooking a large community is a just a "tradeoff". Functional programming is getting more and more popular in the JS world and to just say "Eh, our community is bigger so we're just gonna go ahead and not consider other developers" isn't the right mindset for creating a proposal that's going to affect everyone and the future of JS.

There are type systems, like Hindley-Milner, that have been tried before. Not necessarily in JS but it's not like TypeScript is the only proven type system. And TypeScript has been proven to work for what TypeScript works for, just saying it's "proven" is disingenuous when you're only referring to it working for what it works for (what you use it for). And TypeScript isn't a barrier to entry at all for a type system in JS since TypeScript isn't a part of JS.

This proposal doesn't sit right with me in general. This effort would be much better spent on developing a type system that works for everyone. It really just feels like TypeScript users wanting to shoehorn what they can into JS. If this goes through, it's going to kneecap any future attempt at a real type system.

simonbuchan commented 2 years ago

@kee-oth ok, I keep seeing this response. Why do you feel this is only usable for typescript? There's no reason to think that at all from what I can see.

(Also Elm is apparently the second most popular JS transpiler now, you should look into it)

kee-oth commented 2 years ago

@simonbuchan To be clear, I never said anything about this only being usable for TypeScript. I do, however, believe that TypeScript is being and will be treated as the first class citizen in this proposal. This means that whatever people come up with via these discussions will be heavily favoring TypeScript, maybe not the syntax (but probably) but definitely what "space" needs to be carved out will be how TypeScript does it. If someone proposes an idea that doesn't also work with TypeScript, it will be extremely difficult to get it in, if not just already dead in the water. Other type systems will be welcome so long as they play by the rules that were set by TypeScript's needs.

This proposal will already probably be a bit different than TypeScript because of various restrictions. A TypeScript project is already not going to "just work" in the browser (or Node) so why not start fresh and work from a place that's best for JS and the larger community and not just TypeScript? A great approach for a type system would be to start from scratch with advocates for a JS type system in general and with type system experts working on something extremely ergonomic and usable for all JS use cases. The current proposal is a proposal born from advocates for a particular brand of a type system which isn't necessarily the same as developers who want a type system in JS in general. The goals are different and skewed and therefor the proposal and any solution will have that skew built in.

simonbuchan commented 2 years ago

A great approach for a type system would be to start from scratch with advocates for a JS type system in general and with type system experts working on something extremely ergonomic and usable for all JS use cases

Funny, that already happened at least three times: Actionscript, Flow and Typescript. They all used basically identical syntax, despite having very different semantics (Flow even used Hindley-Milner). We already have the results, adding types to JS looks like this. Are there devils in the details? Of course. But there's plenty of real world examples to look at to see what actual trade-offs you're making, and the fact that the big trade-off here is the billions of lines of Typescript already out there should hardly be a reason to avoid that syntax.

kee-oth commented 2 years ago

Funny, that already happened at least three times: Actionscript, Flow and Typescript. They all used basically identical syntax, despite having very different semantics (Flow even used Hindley-Milner). We already have the results, adding types to JS looks like this.

I disagree. It's safe to assume all three of those projects had their own goals and objectives. Flow is the only one I think may have been actually designed from the start for general purpose JS programming. ActionScript isn't Javascript and is object-oriented. And TypeScript was born heavily leaning towards object-oriented programming and that hasn't really shaken off. So ActionScript and TypeScript's type systems reflect that.

...there's plenty of real world examples to look at to see what actual trade-offs you're making, and the fact that the big trade-off here is the billions of lines of Typescript already out there should hardly be a reason to avoid that syntax.

I don't believe there is much of a trade-off with going with something new vs something TypeScript-y. This proposal will already require codemods to TypeScript codebases to make them work. So we're not going actually have TypeScript in the browser anyway so I don't think this proposal should be shackled to how TypeScript currently is.

simonbuchan commented 2 years ago

And TypeScript was born heavily leaning towards object-oriented programming and that hasn't really shaken off

Uh... citation needed? Typescript from the very start was only implementing existing JavaScript type behavior? They added classes, but only when it was specced by ECMA, and was also implemented by babel and the like. I think you don't have a very good understanding of Typescript and probably shouldn't be making sweeping claims about it's appropriatness here?

kee-oth commented 2 years ago

@simonbuchan

Here's the citation, straight from TypeScript's official website:

TypeScript began its life as an attempt to bring traditional object-oriented types to JavaScript so that the programmers at Microsoft could bring traditional object-oriented programs to the web. As it has developed, TypeScript’s type system has evolved to model code written by native JavaScripters. The resulting system is powerful, interesting and messy.

https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html

simonbuchan commented 2 years ago

@kee-oth that's a weird statement: perhaps they meant it started internally as an attempt at aping traditional C# style types and found that modeling JavaScript behaviors made more sense, because to my knowledge no public version of typescript has had anything I would call traditional OO types.

It's probably illustrative to read the next document up from that link, the one for OO language users: https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-oop.html

It spends all it's time telling you how Typescript doesn't work like traditional OO at all.

Can you point at any particular feature of Typescript that you feel demonstrates:

And TypeScript was born heavily leaning towards object-oriented programming and that hasn't really shaken off.

The only one I can think of is enums, which are basically a fancy set of constants, and not much like C#, for example.

theScottyJam commented 2 years ago

I'm not entirely sure what you're getting at. The class syntax, in its entirety, is something unique to OOP, so any type syntax they add that focues on class syntax is a feature that's specifically tailored towards OOP. So, this includes things like inheritance-related type-checking, abstract classes, public, private, protected, etc.

Are these features implemented in a way that's different from how it's implemented in a "traditional" OOP language like Java or C#? Sure. TypeScript has limitations, so they can't exactly emulate the behavior of C# and Java, and they wanted to make that clear. But, non-traditional OOP doesn't mean non-OOP.

In the end, this is probably just a debate over semantics. What you're defining as OOP is probably a little different that what that author may have had in mind, and that's fine. I think @kee-oth's point still stands though, which is the fact that TypeScript does provide a ton of support for OOP, and less support for those from a more functional background. I'm honestly not sure how much TypeScript could do to support a more functional type-system, they're severely limited in what they can do. But, we might not be as limited in this proposal, so it's possible we could consider things that TypeScript would not have been able to consider, things like type classes, or algebraic data types, etc. TypeScript's success makes it a great starting point, but I see nothing wrong with exploring some of these other ideas as well.

simonbuchan commented 2 years ago

@theScottyJam (and @kee-oth I guess) https://en.m.wikipedia.org/wiki/TypeScript

TypeScript developers sought a solution that would not break compatibility with the standard and its cross-platform support. Knowing that the current ECMAScript standard proposal promised future support for class-based programming, TypeScript was based on that proposal.

Typescript has a few ideas from pre-1.0 days that were not based on standards work, but it seems as they settled on the final behavior and as it took off, they have been extremely careful to avoid adding any behavior or syntax other than erased types. Enums and modules are notable exceptions, but are hardly particularly OO.

jethrolarson commented 2 years ago

After having a long think on this. I don't think my proposal here is good because it's proposing another specific type system. Even though I'd prefer that kind of type syntax, I think that this spec should be completely agnostic of what the metadata is doing and merely provide a way to inline arbitrary metadata within source code with some kind of way of designating what system is being used for that metadata. #80 I think is moving that direction so I've created a branch that explores that.

mindinsomnia commented 2 years ago
:: (string, string, string?, string?) -> string
function stringsStringStrings(p1, p2, p3, p4 = "test") {
    // TODO
}

I like this and think it's better than this proposal, but, suggestion, what about this?..

// (string, string, string?, string?) -> string
function stringsStringStrings(p1, p2, p3, p4 = "test") {
    // TODO
}

Requires literally no change to the language and achieves all the same results.