ES8 Proposal: Optional Static Typing

Brandon Andrews warcraftthreeft at sbcglobal.net
Mon Jul 20 00:08:53 UTC 2015


> My personal feeling is that I’d rather ES not have static typing of this form.  I believe it makes the language substantially more complex, and to me it feels best to try to minimize the growth of the language in order to ensure that those things that we do add are added in a compatible and sound way.  Also, I wonder if static typing wouldn’t work out better if instead of extending ES, you found a way to instead make wasm (https://www.w3.org/community/webassembly/) powerful enough to handle the language that you want.  The benefit of that route is that (1) there is already support behind wasm and what I see as a broad desire to enable wasm to handle statically-typed object-oriented/functional languages, and (2) it’s a clean slate, so a proposal doesn’t have to worry about the compatibility issues.



It's an understandable response. Just looking at the changes required is absolutely daunting in the complexity. The way I view it though is growing the language slowly, without types in place, is actually causing more complexity and limiting the language for future growth. The way TypedArrays and SIMD for instance were approached are needlessly verbose. Now that types are being seen as a requirement the way they're handled is being done poorly. The aversion to topics like operator overloading being the clear candidate when looking at SIMD. The complexity continues when looking at the syntax proposed for class operator overloading in the previous suggestions http://www.slideshare.net/BrendanEich/js-resp/15 . This is something that will continue as more types and features are added. Setting up a framework for types and language features early on will allow these features to be added with clean polyfills.

As much as I like the idea of WebAssembly I don't see it being viable for decades. In the mean-time ECMAScript will still exist and grow as a language. One day it'll be implemented on top of WebAssembly and personally I'd like Javascript to age well. It's rather close to being a very general purpose language. (I use it on the client, server, and database(PLV8) which is pretty awesome. It'll be years before WebAssembly gets to that level).


>> ```js
>> bignum

>> float16/32/64/80/128
>> decimal32/64/128
>> int8x16/16x8/32x4/64x2/8x32/16x16/32x8/64x4
>> uint8x16/16x8/32x4/64x2/8x32/16x16/32x8/64x4
>> float32x4/64x2/32x8/64x4
>> rational
>> complex
>> ```
> Are these types baked into the language, or can these types be user-defined?  It’s fine if they are user-definable but present in the standard library, but it seems weird if these were baked in at the language level.

Yes, they'd be baked into the language as core types. One of the big reasons is that they'd be natively implemented generally by implementations. There is no fast way to implement bignum without a native implementation. I've written bignum algorithms in Javascript before and never got performance anywhere close to native. Same for SIMD operations, thus the SIMD proposal in ES7.

>> In theory the following current keywords could be deprecated in the long-term: Boolean, Number, String, Object, and the TypedArray objects. Their methods and features would be rolled into the new type system.
>
> Deprecating these would be almost impossible given how widely used they are.  We still have to support JS idioms from before ES1 standardization (like function.arguments).

It's something that could co-exist for years along-side the preferred ones. If ES8 has an implementation a warning might exist that would continue for a decade. A lot of web developers have kind of got used to the idea of living specs from HTML5 and CSS's continuous changes. It's not that the features would be removed, they just might not exist forever or in all implementations.


>> Support for resizable typed arrays.
>> ```js
>> var foo:uint8[];
>> foo.push(1);
>> var bar:uint8[] = [1, 2, 3, 4];
>> ```
> Currently, typed arrays are not resizable.  Are you suggesting that this is a separate type from the existing typed array types?


Yes. A resizable typed array is a new types proposed here. What we have right now would be essentially a var foo:any[] = [1, 'foo', {}]; example. A resizable typed array, like the current type array, has finer control for allocation with the advantage of native implementation speed. I was talking to people comparing this to C++'s Vector class. If it should have things like a capacity would be up for debate if it's even worth it.


>> Support for fixed-length typed arrays:
>> ```js
>> var foo:uint8[4];
>> foo.push(0); // invalid
>> foo.pop(); // invalid
>> var bar:uint8[4] = [1, 2, 3, 4];
>> ```
> How do you propose to handle:
> ```js
> var foo:uint8[4] = …;
> var bar:uint8[8] = …;
> if (p) return foo; else return bar;
> ```
> Is this a type error, or does the function have some type?  This is an important question.  Usually, non-resizable arrays do not store the length as part of the type, since preserving a relationship between length and type is generally a hard problem.  The trivial approach - making the above be a type error - was done in Pascal and it was very restrictive.  For example, it makes it hard to write a reusable function over non-resizable arrays, since every user will have to agree on the length.

You're exactly right about them not storing the length. I view fixed-sized arrays as a high performance use-case. If the user were to use your example I might say that the function signature would need to define either a resizable array or a fixed sized array of the maximum size. The resizable would probably be the default case essentially converted from a fixed size to a resizable array.

```js

function Foo(p:boolean):uint8[] // default case

{
    var foo:uint8[4] = ...;
    var bar:uint8[8] = ...;
    return p ? foo : bar;

}
```
```js
function Foo(p:boolean):uint8[8] // return a resized foo
{
        var foo:uint8[4] = ...;
        var bar:uint8[8] = ...;
        return p ? foo : bar;
}
```
Probably for someone concerned with performance I'd probably not recommend using such code anyway. While it does give the Javascript engine all it needs to figure out what to do optimally they'd need to actually implement it sanely.

>> The ability to type any variable including arrow functions.
>> ```js
>> var foo:uint8 = 0;
>> var foo = uint8(0); // Cast
>>
>> var foo:(int32, string):string; // hold a reference to a signature of this type
>> var foo = (s:string, x:int32) => s + x; // implicit return type of string
>> var foo = (x:uint8, y:uint8):uint16 => x + y; // explicit return type
>>
>> Function signatures with constraints.
>>
>> function Foo(a:int32, b:string, c:bignum[], callback:(bool, string) = (b, s = 'none') => b ? s : ''):int32 { }
>>
>> Simplified binary shifts for integers:
>>
>> var a:int8 = -128;
>> a >> 1; // -64, sign extension
>> var b:uint8 = 128;
>> b >> 1; // 64, no sign extension as would be expected with an unsigned type
>>
>> Destructing assignment casting:
>>
>> [a:uint32, b:float32] = Foo();
>> ```
> What do you propose should happen if Foo does not return these types?

Either an implicit cast is performed or a type error is raised. It would need to be debated on which implicit casts should exist. Personally I like implicit casts for as many things as possible where they make sense. For instance, I like string to integer implicit conversions while others would cry fowl and say parseInt(x, 10) is the only true way. There are obvious cases though where a type error is a clear winner and can be caught at run-time:

```js

var foo:(int32):int32[] = (i) => i * 2;
foo(42); // No suitable cast exists from int32 to int32[].

```


>> Function overloading:
>> ```js
>> function Foo(x:int32[]) { return "int32"; }
>> function Foo(s:string[]) { return "string"; }
>> Foo(["test"]); // “string"
>> ```
> Currently, a function declaration causes the creation of a variable that points to that function.  What would the Foo variable point to in this case?

Internally it could be a few things. An array of function signatures would work. When using the () operator it would access the closest matching signature. If someone wanted to reference specific functions there could be syntax for that, but it's not something that's usually required.

>> Generic functions:
>> ```js
>> function Foo<T>(foo:T):T
>> {
>>        var bar:T;
>> }
>> ```
>> Generic classes:
>> ```js
>> class Vector2d<T>
>> {
>>        x: T;
>>       y: T;
>>        constructor(x:T = 0, y:T = 0) // T could be inferred, but that might be asking too much. In any case T must have a constructor supporting a parameter 0 if this is a class.
>>       {
>>                this.x = x;
>>                this.y = y;
>>     }
>> }
>> ```
>> Generic constraints aren't defined here but would need to be. TypeScript has their extends type syntax. Being able to constrain T to an interface seems like an obvious requirement. Also being able to constrain to a list of specific types or specifically to numeric, floating point, or integer types. Another consideration is being able to support a default type. Also generic specialization for specific types that require custom definitions. There's probably more to consider, but those are the big ideas for generics.
> Do you propose that generics behave as if there was type erasure under the hood, or as if every instantiation splats out a new class?  If the latter, what does the expression “Vector2d” without any instantiation give?  The challenge here is that your proposal in general, and generics in particular, seem like they would have trouble interoperating with old ES code that does not have types.  I think the big problem to solve here is how to make generics work properly while still being optional.

Type erasure seems fine creating a new class. I mentioned specialization. So Vector2d without any suitable generic would throw a type error unless one defined a default. Either by doing:
```js
class Vector2d
{
    x:any;
    y:any;
}
```
or 

```js
class Vector2d<T = uint32>
{

}
```
(Or some other syntax).


Programmers can be odd and implement specializations differently (different members) which could complicate things also. Regarding old code, a lot of old code and dynamically typed code will essentially be interpreted as having "any" or "number" depending on the context. It shouldn't cause huge issues. I mentioned before that number would have implicit casts to the new types. You might need to wrap parameters in a cast to choose a certain overload if the wrong one is chosen implicitly.


More information about the es-discuss mailing list