sirisian / ecmascript-types

ECMAScript Optional Static Typing Proposal http://sirisian.github.io/ecmascript-types/
453 stars 4 forks source link

How to ensure that types aren't completely run-time and that engines can use them in compile-time. #29

Open sirisian opened 6 years ago

sirisian commented 6 years ago

Refer to:

https://esdiscuss.org/topic/proposal-optional-static-typing-part-3#content-11 https://esdiscuss.org/topic/proposal-optional-static-typing-part-3#content-15 https://esdiscuss.org/topic/proposal-optional-static-typing-part-3#content-16 https://esdiscuss.org/topic/proposal-optional-static-typing-part-3#content-17

An inherent problem with adding types in Javascript that are performant is that classs, functions, and basically everything is dynamic and can be changed. This means that given a variable x the definition could change throughout the program. While this makes optimizing difficult it also makes tooling difficult since it can be impossible for tools to infer a type that's changing with function calls. Some type inference can work, but not always.

An example of Javascript's dynamic property can be seen with the example:

class A {}
class B {}
[A, B] = [B, A]; // Swap A and B

One can stop this by using const:

const A = class A {};
const B = class B {};
// [A, B] = [B, A]; // Illegal, A and B are already assigned

This doesn't stop them from being changed in other ways, like modifying properties and methods. Freezing the object would stop that though. The missing piece is to make the whole prototype chain const and freeze it, thus removing anything that can be changed. So Object is made const and frozen:

var tempObject = window.Object;
delete window.Object;
const Object = tempObject;
delete window.tempObject;
Object.freeze(Object.prototype);

This then allows one to write:

const f = function(a:A)
{
    a.x = 0;
}
const A = new class
{
    x:uint8 = 5;
}
f(new A()); // A is dynamic and the interpreter is forced to use a run-time check.

Object.freeze(A); // A is both const and frozen making it no longer dynamic. Since the dynamicness was removed it follows that the engine could optimize f doing essentially a compile-time check

f(new A()); // Compile-time verification for all instances of f where the argument is typed or the type can be inferred.

I've been calling these "compile-time" optimizations "const freeze-time" optimizations. That is when an object and its prototype chain is known to be const and frozen then the engine can handle certain cases faster where the input is a known type that matches the parameter.

Problems with this are as follow that need solutions:

  1. Writing classes everywhere using class expressions with const is wordy and would be confusing for beginners. One could introduce:

    const class A {}
    Object.freeze(A);

    It's not that much better though and the freeze step is still tacked on later when in many cases one is fine freezing the class after defining it.

  2. Classes are still dynamic until Object.freeze is called which could happen anywhere. That means whenever Object.freeze is called the engine is forced to reevaluate the code finding everywhere its used and optimizing. For sane libraries that define everything ahead of time and freeze them this isn't a huge problem. The largest issue is since class definition and Object.freeze are separate distinct operations that tooling could struggle to follow the types in some cases. Imagine you pass your new class off to a function that adds properties based on a file and then freezes it. The tooling would have a difficult time working with that. Is it important to handle those cases?

One useful language feature would be the ability to define a const frozen class that would make such guarantees. Object.seal already exists with different meaning and using "freeze" doesn't imply const.

<keyword> class A {}
// or a new class keyword
<keyword> A {}
// or a token
<token>class A {}
class<token> A {}
// or something else

I haven't figured out a good syntax that is consistent and clean for something that would be used in every library and project hundreds of times.

PierBover commented 6 years ago

This is elegant and solves the problems described earlier:

<keyword> class A {}

For the keyword I was thinking about final and sealed, although since these are used in Java and C# I'm not sure if that would be a great idea. These keywords have different meanings in these languages than "const-freeze".

What about?

// To follow the same metaphor as Object.freeze()
frozen class A {}
// seems to convey the meaning of something that cannot be changed
stable class A {}
krazov commented 6 years ago

Well, class has different meaning in other languages, too. I like final.