tc39 / proposal-type-annotations

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

Which parts of the proposal should've native support from JavaScript? #196

Open msadeqhe opened 9 months ago

msadeqhe commented 9 months ago

Let's find out which part of TypeScript, Flow, etc can be a part of JavaScript without type annotations. Which features of TypeScript, Flow, etc can be used without radically changing the syntax of JavaScript?

1. Interfaces have their spot in JavaScript.

It's reasonable to have interface language construct in JavaScript, because it's not necessary restricted to type checkers! in a way that an interface can be declared without any type annotation. That's it. Interfaces are somehow abstract classes, and they can be used to help us to declare other classes. On the other hand, we need implements keyword to implement interfaces within classes. Fortunately both interface and implements have been reserved keywords in JavaScript for the future use, so there will not be any source compatibility issue at all. For example:

interface Xyz { /*...*/ }

class Base { /*...*/ }

class A extends Base implements Xyz { /*...*/ }

Also with the native support from JavaScript (not as comment), we may import and export interfaces similar to classes.

2. Optional arguments and properties with ?: syntax

Both of them can be safely ignored even without type annotations:

// With type annotation
function name(arg?: string) { /*...*/ }
// Without type annotation
function name(arg?) { /*...*/ }

interface Xyz {
    // With type annotation
    property?: number;
    // Without type annotation
    property?;
}

EDIT: Ternary operators cannot be in argument list of function declarations. So arg? doesn't break any JavaScript program.

3. Non-nullable assertions with !. operator

It's complementary to ?. operator:

let x = obj!.member;

4. Generic argument list

The < and > operators are not allowed in class, function and variable declarations. So JavaScript can treat them as comment instead of reporting a syntax error:

class A<comment> { /*...*/ }

function B<comment>() { /*...*/ }

Which features from TypeScript, Flow, etc are too much related to type annotations?

type (in TypeScript, Flow, etc) and opaque type (in Flow) features are definitely too much related to type annotations.

satisfies and as features in TypeScript, should be treated as :-style comments, because they are extensively related to types. Respectively their syntax can be changed to:

In this way, both satisfies and as are not necessary to be keywords. JavaScript syntax should not be changed as much as possible. New keywords may make some existing programs to break.

And any other part of the syntax from TypeScript, Flow, etc which are for type annotations, should be changed to :comment syntax in JavaScript (with simple rules as much as possible).

egasimus commented 9 months ago

5. package

I'd like to add another previously reserved keyword, package, to the list of existing things which could be put to use for the type syntax.

package could be used to define type namespaces, write ES modules inline, delineate subsections to be delegated to a specific compiler (be it TS, Flow, JSX...) - somewhat equivalent to mod in Rust. Example on https://github.com/tc39/proposal-type-annotations/issues/176#issuecomment-1732415685

azder commented 9 months ago

It may seem like I'm focusing on the negatives.

I'm just trying to consider a class of people who don't want or can't use static types annotations for reasons. Howwill they can continue using JS without or with less friction? E.g. how would they need to adjust code they've imported or copied? Would it be easy to focus on implementation if tag soup is inlined or pre-lined (like JSDoc)?

In short summary: 1.interface keyword - 👎 see a)

  1. ?. - 👎 tag soup inlined b), but also more overloaded meaning of the character sequence ?:
  2. !.- 👎 not complementary to ?. see c)
  3. <> - 👎 is awful looking syntax we can't escape from due to inertia, so only b) is an objection
  4. package - 👎 see a)

a) The TL;DR: that Jurassic Park quote "preocupied with could, didn't think if should". But, even if we use it, should it be in the same way it was used in other languages? I've talked about sedimentation a bit here https://github.com/tc39/proposal-type-annotations/issues/176#issuecomment-1732518449

b) Inlined tag soups are something that would add more problems for dealing with edge cases in the EcmaScript syntax and still potentially limit how another typing language can be inserted. Think of how RegExp is a language you can use in many places, but with not having it being defined as to what syntax it has, but be open to many different ones in the future.

Also, separation of concerns. I'd think the concern of specifying types should not be mixed up in the same place with the concern of implementation. Think haskellian, two lines instead of one:

identityFunction :: Type -> Type -- type info is separate from implementation detail, different block, file, different syntaxes
identityFunction x = x -- the implemenation detail will remain the same, no new syntax headaches

c) The thing about ?. is that it is a runtime operator, not a compile time type assertion. So, they aren't complementary.

With regard to run time, !. in EcmaScript already exists as . and [] - both of which will throw a TypeError if you try to access a property on null or undefined value. In code this is not necessary because you can just use the (6a) networking truth and add a function.

E.g. if I want to have a safe default, I'd use

const asString = $ => null === $ || undefined === $ ? '' : $; // write once

const email = asString(inputEmail).toLowerCase(); // use everywhere

With regard to compile time, same argument as b)

egasimus commented 9 months ago

The simpler reason why interface or package wouldn't fly, elegant as reusing some of those old reserved keywords might seem, is that runtimes generally don't expect them to be keywords and treat them as identifiers. So, still a breaking change in the language.

I'm reminded of "use strict" (and current incarnations like "use server") as examples of other ways to upgrade JS in an at least somewhat backwards-compatible way. But AFAIK that changed semantics, not syntax - strict mode programs would be valid on a pre-strict runtime, only incorrect?

So far comment-based approaches remain the only ones which solve for backwards compatibility and for truly optional adoption.