ES8 Proposal: Optional Static Typing

Brandon Andrews warcraftthreeft at sbcglobal.net
Sat Jul 18 19:35:55 UTC 2015


ES8 Proposal: Optional Static Typing

It was once said by Brendan Eich that 'Types are hard. This doesn't mean "no, never".' It's been a while since ES4. With ES6's TypedArrays and classes finalized and ES7 SIMD getting experimental tests, ECMAScript is in a good place to finally discuss types again. The demand for types as a different approach to code has been so strong in the past few years that separate languages have been created to deal with the perceived shortcomings. Types won't be an easy discussion, nor an easy addition, since they touch a large amount of the language; however, they are something that needs rigorous discussion. I'm hoping this initial proposal can be a way of pushing the ball forward. Turning this into an official proposal discussed by TC39 is the goal. This could very well be most of ES8 due to the complexity.

Since it would be potentially years before this would be implemented this proposal includes a new keyword "enum" for enumerated types and the following types:

number
bool
string
object
int8/16/32/64
uint8/16/32/64
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
any
void

These types bring ECMAScript in line or surpasses the type systems in most languages. For developers it cleans up a lot of the syntax, as described later, for TypedArrays, SIMD, and working with number types (floats vs signed and unsigned integers). It also allows for new language features like function overloading and a clean syntax for operator overloading. For implementors, added types offer a way to better optimize the JIT when specific types are used. For languages built on top of Javascript this allows more explicit type usage and closer matching to hardware.

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.

One of the first complications with types is typeof's behavior. All of the above types would return their string conversion including bool. (In my experience "boolean" is seen as verbose among C++ and C# developers. Breaking this part of Java's influence probably wouldn't hurt to preserve consistency for the future).

The next few parts cover type features that should be supported. The examples aren't meant to be exhaustive but rather show parts of the language that require new grammar and discussion.

Support for nullable types.

var foo:uint8? = null;

Support for resizable typed arrays.

var foo:uint8[];
foo.push(1);
var bar:uint8[] = [1, 2, 3, 4];

Support for fixed-length typed arrays:

var foo:uint8[4];
foo.push(0); // invalid
foo.pop(); // invalid
var bar:uint8[4] = [1, 2, 3, 4];

The ability to type any variable including arrow functions.

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();

Function overloading:

function Foo(x:int32[]) { return "int32"; }
function Foo(s:string[]) { return "string"; }
Foo(["test"]); // "string"

Constructor overloading:

// 4 byte object
value class MyType
{
    x:float32; // Able to define members outside of the constructor
    constructor(x:float32)
    {
        this.x = x;
    }
    constructor(y:uint32)
    {
        this.x = float32(y) * 2;
    }
}

Number would convert implicitly with precedence given to decimal, float128/80/64/32/16, uint64/32/16/8, int64/32/16/8. (Or whichever order makes the most sense). As an example using the MyType class:

var t:MyType = 1; // float32 constructor call
var t:MyType = uint32(1); // uint32 constructor called

Implicit constructors could also be added to the proposed SIMD classes to go from a scalar to a vector.

var v:float32x4 = 1; // Equivalent to an ES7 SIMD splat, so var v = float32x4(1, 1, 1, 1);

Implicit array conversion would also exist:

var t:MyType[] = [1, 2, 3, uint32(1)];

Types would function exactly like you'd expect with decorators in ES7, but with the addition that they can be overloaded:

function AlwaysReturnValue(value:uint32)
{
    return function (target, name, descriptor)
    {
        descriptor.get = () => value;
        return descriptor;
    }
}
function AlwaysReturnValue(value:float32) { /* ... */ }

Class example and operator overloading:

class Vector2d
{
    x: float32;
    y: float32;
    constructor(x:float32 = 0, y:float32 = 0)
    {
        this.x = x;
        this.y = y;
    }
    Length():float32
    {
        return Math.sqrt(x * x + y * y); // uses Math.sqrt(v:float32):float32 due to input and return type
    }
    get X():float64 // return implicit cast
    {
        return this.x;
    }
    set X(x:float64)
    {
        this.x = x / 2;
    }
    operator +(v:vector2d)
    {
        return new vector2d(this.x + v.x, this.y + v.y);
    }
    operator ==(v:vector2d)
    {
        // equality check between this and v
    }
}

Partial class in MyClass.js defining extensions to Vector2d:

class Vector2d
{
    operator ==(v:MyClass)
    {
        // equality check between this and MyClass
    }
    operator +(v:MyClass)
    {
        return v + this; // defined in terms of the MyClass operator
    }
}

Enumerations with enum that support any type except function signatures.

enum Count { Zero, One, Two }; // Starts at 0
var c:Count = Count.Zero;

enum Count { One = 1, Two, Three }; // Two is 2 since these are sequential
var c:Count = Count.One;

enum Count:float32 { Zero, One, Two };

Custom sequential function (these aren't closures):

enum Count:float32 { Zero = (index, name) => index * 100, One, Two }; // 0, 100, 200
enum Count:string { Zero = (index, name) => name, One, Two = (index, name) => name.toLowerCase(), Three }; // "Zero", "One", "two", "three"

Index operator:

enum Count { Zero, One, Two };
Count[0];

Get enum value as string:

// Not sure what the syntax for this would be.

Generic functions:

function Foo<T>(foo:T):T
{
    var bar:T;
}

Generic classes:

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.

Typedefs or aliases for types are a requirement. Not sure what the best syntax is for proposing these. There's a lot of ways to approach them. TypeScript has a system, but I haven't see alternatives so it's hard for me to judge if it's the best or most ideal syntax.

Undecided Topics:

I left value type classes out of this discussion since I'm still not sure how they'll be proposed. Doesn't sound like they have a strong proposal still or syntax.

Unions are another topic not covered mostly because the syntax is very subjective. Without an added keyword the following might work in the grammar. The example uses an anonymous group unioned with an array of 3 elements. Using a class with x, y, and z members would also work. (Or using an interface syntax if one is added could work).

class Vector3d
{
    {
        {
            x: float32;
            y: float32;
            z: float32;
        }
        a: float32[3];
    }
}

Another example would be:

class Color
{
    {
        {
            Red: float32;
            Green: float32;
            Blue: float32;
            Alpha: float32;
        }
        Vector: float32x4;
    }
}

The last topic not covered is if there should be new syntax for TypedArray views.

var a1:uint32[] = [2, 0, 1, 3];
var a2:uint64[] = a1(1, 2); // Creates a view of a1 at offset 1 with 2 elements. So [0, 1].

That might be asking too much though. In a very compact form:

var foo = ((uint64[])a1(1, 2))[0]; // foo would be 1

This has been brought up before, but possible solutions due to compatability issues would be to introduce "use types"; or since ES6 has them Brenden once suggested something like:

> import {int8, int16, int32, int64} from "@valueobjects";

This concludes my proposal on types and the many facets of the language that would be potentially touched. The goal is essentially to turn this, or something similar, into a rough draft. Essentially build a foundation to start from expanding on edge cases and changes required in each part of the language. I'm sure with enough minds looking at each section this could be very well defined by the time ES8 is being considered.

Previous discussions:
This one contains a lot of my old thoughts (at least the stuff from 8 months ago). https://esdiscuss.org/topic/proposal-for-new-floating-point-and-integer-data-types
https://esdiscuss.org/topic/optional-strong-typing

https://esdiscuss.org/topic/optional-argument-types


More information about the es-discuss mailing list