iJS CONFERENCE Blog

TypeScript versus JS++: JavaScript typing done right!

Mar 13, 2019

Roger Poon started JS++ eight years ago with a deep understanding of corner cases that he doesn’t believe the TypeScript team can rival. This is why Roger believes JS++ is the better option whether you need to write a simple library or if you need backwards compatibility with a large, complex, legacy enterprise codebase. Let’s have a closer look at it!

“JS++ is seamlessly compatible with all JavaScript libraries, frameworks, and APIs”

JAXenter: What is JS++ and why should developers use it? What are the immediate benefits?

Roger Poon: JS++ is a superset of JavaScript (ECMAScript 3 specification) featuring a sound gradual type system. While alternatives to JS++ have existed from large companies (including Microsoft TypeScript and “Safe TypeScript” from Microsoft Research; Google AtScript and SoundScript; and Facebook Flow), these alternatives have never been able to achieve “soundness” in their type system. Here’s another explanation of soundness, specifically he describes:

“… Java’s type system is unsound if, say, without casting or other ‘unsafe’ operations, we could have a compile-time Integer that is actually a runtime String.”

(Please note this is not the actual case in Java, just an illustrative example.)

When you declare a data type in JS++, it is guaranteed to be correct. Furthermore, it is guaranteed to be correct during compile time analysis and runtime execution after the application has been released.

In contrast, the popular alternative for adding types to JavaScript, Microsoft TypeScript, has over 72,000 results on Stack Overflow relating to TypeErrors alone that occur at runtime… after successful compiles. The reason is because TypeScript’s type system is unsound, and you should not expect the same runtime behaviors you’d expect from other programming languages where declaring types can guarantee correct program behavior as it relates to types – such as in Java. In TypeScript, this is known as “type erasure” (in contrast to JS++’s “type guarantees”), and TypeScript describes soundness as not a goal. Thus, in Microsoft TypeScript, it’s possible to declare your types and then have the application crash with a type error on the declared types.

Type errors are a major class of errors in JavaScript. Here’s a screenshot where GoDaddy lost sales revenue due to a runtime ‘TypeError’ (highlighted) that prevented the PayPal checkout from proceeding. Here’s a screenshot of Google Image Search being broken with lots of runtime TypeErrors; it’s not evident from the screenshot, but the Google error occurred on Easter Sunday 2018, when their team was on holiday.

[marketing_widget_area 3]

International JavaScript Conference: How does JS++ work? 

RP: JS++ extends the JavaScript language with new features, specifically JavaScript as standardized in the ECMAScript 3 specification. (JS++ was first released in October 2011 so it pre-dates ECMAScript 6 by four years, TypeScript by one year, etc. At the time, Internet Explorer 6 was still important for backwards compatibility, so ECMAScript 3 was chosen over ECMAScript 5 for maximum compatibility. This also allows us to address legacy enterprise web applications even today.)

JS++ works by compiling to ECMAScript 3 JavaScript. As a result, JS++ works wherever JavaScript works. This ranges from Internet Explorer 6 to the latest Chrome and iOS/Android browsers, but it also includes Node.js server applications, Windows Script Host (WSH) macros, Arduino, system utilities, game scripting, and more.

We spent the most time on the type system design, which dates back to work beginning in October 2011, exactly one year before Microsoft TypeScript was announced. We have an advanced type system that enables sound gradual typing at scale and efficient out-of-bounds error analysis. Unlike other JavaScript supersets, we have a focus on correctness. One of our cornerstone features (“type guarantees”) enables developers to correctly and gradually migrate dynamically-typed code to static typing (which is a much harder problem to solve than the reverse: enabling dynamic typing on mostly statically-typed code which, in most cases, are already well-typed).

JS++ works by compiling to ECMAScript 3 JavaScript. As a result, JS++ works wherever JavaScript works.

iJS: What are “type guarantees” in JS++? How does it differ from Microsoft TypeScript (the popular alternative for adding types to JavaScript)?

RP: Although we compile to JavaScript, the cornerstone of the JS++ language is “type guarantees.” In other words, when you declare your data types, they are guaranteed to be correct during compile time checking and runtime code execution. “Type guarantees” mean that JS++ is more akin to C, C++, Java, and C# than JavaScript/TypeScript because declaring the data types will change the semantics of your program.

Consider a Java program where a variable is declared as ‘int’ versus ‘String’. The operations allowed on the variable will change. The compiler will reject your programs and give you an error if you try a subtraction with a variable declared as ‘String’ but not for ‘int’.

Here is a correct Java program: https://ideone.com/QzcUfE

Here’s an incorrect Java program: https://ideone.com/IHTf8X

The incorrect program is not even allowed to compile. Thus, since no program was compiled or built, there is no program to run. In contrast, JavaScript and Microsoft TypeScript can allow incorrect programs to compile, run, and even be distributed. Here are four lines of TypeScript that will fail at runtime (when the application is running) with a type error but will pass the compiler’s checking:

var func:any = function() {};
var str1:string = func;
var str2:string = str1.toLowerCase(); // TypeError: undefined is not a function
console.log(str2); // Never runs. Program already crashed from TypeError.

Here are 2-3 lines of JavaScript that will fail at runtime:

// Simple variable declaration. Innocuous, right?
var a = typo; // ReferenceError: typo is not defined
console.log("Script started"); // Never runs. Script already crashed with ReferenceError.

Microsoft considers “a sound and ‘provably correct’ type system” (exact quote) to be a non-goalhttps://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

There are pros and cons to both approaches. With TypeScript, you get better IntelliSense if your codebase is mostly JavaScript rather than a JavaScript superset. With JS++, you get accurate analysis for type errors and predictable application runtime behavior that conforms with the types you declared. While we haven’t delivered it yet, JS++ is capable of IntelliSense but only for the “JS++ code” (e.g. declared with “unsigned int”, “class”, “interface”, etc.) rather than the “JavaScript code” (e.g. declared with “var” and “function”).

iJS: What are some of the corner cases in JavaScript that TypeScript has trouble handling?

RP: I started JS++ in 2011 with a deep understanding of corner cases that I don’t believe the TypeScript team can rival. This is why I believe JS++ is the better option whether you need to write a simple library or if you need backwards compatibility with a large, complex, legacy enterprise codebase.

For example, consider the following code (which is tested in IE6 for Windows XP):

var x = new ActiveXObject("WScript.shell");
alert(typeof x);       // "object"
alert(typeof x.run);   // "unknown"
x.run("explorer.exe"); // Opens Windows Explorer

Notice how ‘typeof’ returns “unknown”. MSDN does not document an “uknown” type for ActiveXObject. Clearly, the code seems to be written correctly; if executed, it will open Windows Explorer. This code is indeed correct and conforms with the ECMAScript 3 specification. ECMAScript 3 Section 11.4.3 “The typeof Operator” allows an “implementation-dependent” result for host objects, which includes ActiveXObjects, DOM objects, etc.

In another case, TypeScript uses structural typing. JS++ uses nominal typing. Structural typing doesn’t consider the case where an ‘Array’ (case-sensitive) in a parent page is not the same ‘Array’ (case-sensitive) in an iframe of the parent page. If the ‘Array’ inside the iframe is extended or changed, the ‘Array’ of the parent page may not necessarily change; thus, the structure of ‘Array’ in the iframe should not match the ‘Array’ of the parent page during static code analysis. Yet, this is very difficult to detect during static analysis, and TypeScript can mistakenly pass a type check here. In contrast, JS++ ‘external’ will remain correct.

JS++ can be used wherever JavaScript can be used, but we caution that JS++ has not reached 1.0 yet.

In yet another case, TypeScript’s lib.d.ts incorrectly returned ‘NodeList’ for the DOM API’s ‘getElementsByTagName’ (proof). This makes IntelliSense inaccurate, this makes the compiler’s analysis inaccurate, and this risks user’s code crashing at runtime as a consequence. At the time Microsoft made this error, Firefox did not implement ‘getElementsByTagName’ as returning ‘NodeList’ but other browsers did. Mozilla insisted that this function should return an “HTMLCollection” because this is correct according to the DOM API specification despite the fact that all other web browsers implemented it differently. This bug was filed in 1999 and was marked as “WONTFIX” by Mozilla. They could have fixed this for 20 years and chose not to.

These are fundamental design errors TypeScript made from failing to understand JavaScript and ECMAScript corner cases. An investment into TypeScript means accepting all the problems – now and in the future – that could have been avoided by choosing JS++. This results in technical debt that could have been avoided altogether. JS++ will work for Windows Script Host, buggy implementations of the DOM API, buggy garbage collector implementations where the data (and thus the types) change because an object was incorrectly collected early, nuances of the ECMAScript language specification, and so on.

iJS: When should JS++ be used? 

RP: JS++ can be used wherever JavaScript can be used, but we caution that JS++ has not reached 1.0 yet. We’re close, but there are still edges to iron out.

JS++ is seamlessly compatible with all JavaScript libraries, frameworks, and APIs (such as the Document Object Model API for scripting the web browser).

Use cases:

  • Node.js (server applications)
  • Frontend web development
  • Windows Script Host (WSH) macros
  • Migrating complex, legacy enterprise JavaScript applications (such as corporate intranet applications)

SEE ALSO: State of JavaScript 2018: Angular’s downfall and new up-and-comers

iJS: What’s next for JS++?

RP: Version 1.0 and IntelliSense. These are the two biggest things on the horizon for JS++, and they are the most requested by our users.

iJS: What is an out-of-bounds error?

RP: An out-of-bounds error occurs when you try to access a container element that does not exist in the container.

iJS: How does JS++ prevent out-of-bounds errors?

RP: JS++ analyzes out-of-bounds errors using “existent types.” An existent type describes whether a container access is within-bounds or out-of-bounds, and it guides code generation. We have shown that, for projects ranging from 1,000 lines of code to 6,000 lines of code, existent types add only ±1 ms (millisecond) overhead to compile times.

Compile times are a very important part of the user experience for JS++. By using traditional nominal typing, existent types avoid following every branch, loop, virtual method call, and other complexities that can cause compile times to exponentiate or – in the worst case – run into the Halting Problem.

When used correctly, existent types will prevent the application from terminating prematurely via abort(), segfaults, uncaught exceptions, runtime errors, etc.

iJS: Does TypeScript prevent out-of-bounds errors?

RP: No, a lot of languages can’t. However, in our latest benchmarks, we showed that you can compile 6,000 lines of JS++ code across 70 files – with sound type checking and full analysis of out-of-bounds errors – four times over in the time it takes TypeScript to compile 3 lines of code and get the types wrong. Here’s the TypeScript code and proof:

$ ./node_modules/typescript/bin/tsc --version
Version 3.2.2
$ cat test.ts 
const xs: number[] = [1,2,3];
let x:number = xs[1000]; // number... even with --strictNullChecks
console.log(x);
$ time ./node_modules/typescript/bin/tsc --strictNullChecks test.ts 

real    0m1.084s
user    0m1.692s
sys    0m0.016s
$ node test.js 
undefined

However, it should be said that out-of-bounds errors have existed since the first high-level programming language, Fortran, and continues to be a problem today.

In contrast, in the latest release of JS++, here are our compile times with out-of-bounds error analysis:

Compile times for 0.9.0 – detection of out-of-bounds at compile time

 

iJS: JS++ is described as having “nearly no learning curve.” Can you elaborate on this?

RP: Once you learn “internal types” (JS++ types), “external types” (JavaScript types, although technically it describes more), and the conversions between them, the rest of JS++ should be intuitive for programmers with backgrounds in C, C++, Java, and C#.

For example, function overloading in JS++ is what you would expect: multiple function declarations with the same name. In TypeScript, users run into surprises where one function body handles all constructor overloads. Furthermore, JS++ has 8-, 16-, 32-, and 64-bit signed and unsigned integer types, so there aren’t any ‘NaN’ surprises that arise when all your numbers are IEEE-754 doubles as in JavaScript/TypeScript.

SEE ALSO: Pluralsight 2018 wrap-up: JavaScript reigns supreme, Dart gains momentum

iJS: Can you describe the migration path from JavaScript to JS++?

RP: Due to its advanced type system, JS++ can be one cog in a larger system. There is no vendor lock-in. For example, your JS++ commitment can be one file, one class, one module, one project, or even one line of code. JS++ is backwards- and forwards-compatible with JavaScript. It is designed to be seamlessly compatible with the rest of your system whether it’s JavaScript, TypeScript, etc – without vendor lock-in.

In contrast, TypeScript requires steep commitment due to its unsound type system. Before you can reap the full benefits of TypeScript, you have to add “typings” to almost all your code before it’s able to perform meaningful analyses. One company described their migration of 30,000 lines of JavaScript to TypeScript, and their independent conclusions were:

  • You can’t always trust the compiler
  • Type inference doesn’t go very far
  • TypeScript requires many type annotations
  • Types are still your assumptions
  • Typing can’t always be incremental
  • Circular dependencies

All of the problems listed above (except circular dependencies) are side effects of an unsound type system – everything from being unable to trust the compiler to requiring lots of type annotations before the tool becomes useful. It needs to be re-emphasized that JS++ can start becoming useful with one type annotation. We do not require you to rewrite your entire code base. They specifically highlighted that TypeScript failed incrementally (so you’re not going to just use it on 1% of a 1 million line code base), required many type annotations before it became useful (again, you’re not going to use it on 1% of your code successfully – even though technically you can, and Microsoft will tout this as a win without discussing the theoretical concerns), the risk of human error (e.g. I can declare a string but accidentally tell TypeScript it is a number), and so on.

An investment into TypeScript means accepting all the problems – now and in the future – that could have been avoided by choosing JS++

The problems they faced with the TypeScript compiler and type system don’t just grow linearly. Logically, they would exponentiate at scale if you think A is a number (but it’s actually a string at runtime) and the variables B, C, D, E depend on A and have further dependencies themselves.

It’s also interesting to note how they were bitten by a TypeScript breaking change:

“TypeScript doesn’t allow circular dependencies. Sort of. TypeScript was happily compiling our code, circular dependencies and all, for months until one day it broke.”

Despite having reached version 1.0 nearly 5 years ago, Microsoft TypeScript still routinely makes breaking changes on each new release. Meanwhile, JS++ automatically handles circular dependencies, automatically resolves complex cross-file import and inheritance hierarchies, and almost never makes breaking changes. I provide an explanation of the engineering behind how we efficiently handle circular dependencies – even in the presence of ambiguities – here.

In JS++, you don’t need to figure out all the types in your existing code. You only need to add types going forward in your new code or incrementally add types one-by-one to your existing code. Our Getting Started guide teaches both these techniques. You can also write a library in JS++ that can be consumed in JavaScript, and the types that you declared by writing the library in JS++ will be “preserved” even though you’re using the library from JavaScript via the toExternal/fromExternal design pattern. Finally, you can import an entire JavaScript library to JS++ using the external statement.

As a consequence, JS++ can be used in one file, one library, one class, one module or scale to your entire project. Thus, since JS++ works so well from the smallest commitment to the largest commitment, there is almost no vendor lock-in and no future risk of vendor lock-in.

Thank you!

Sign up for the iJS newsletter and stay tuned to the latest JavaScript news!

 

BEHIND THE TRACKS OF iJS

JavaScript Practices & Tools

DevOps, Testing, Performance, Toolchain & SEO

Angular

Best-Practises with Angular

General Web Development

Broader web development topics

Node.js

All about Node.js

React

From Basic concepts to unidirectional data flows