AtScript has been replaced with TypeScript; see the blog post.
This document is historical.
Status: Abandoned
Author: misko@google.com
This document is published to the web as part of the public Angular Design Docs folder
JavaScript, the de facto language of the browser, has a large, thriving community. However, it is missing some features which would make it much more manageable for large-scale application development. The goal of AtScript is to enhance the language with these missing features without infringing upon its current capabilities.
Nominal types were originally proposed for JavaScript in the ECMAScript 4 specification. Though this was never implemented in JavaScript, it was adopted and widely used via Adobe's ActionScript3 and inspired others. e.g. TypeScript adopted the syntax, but uses a structural type system semantic.
Our proposal is to use this previously vetted syntax by using a ':' and type declaration after the variable, parameter or function as seen below. Notice that translation to ES6 simply strips the type information.
AtScript | ES6 |
class MyClass { methodA(name:string):int { var length:int = name.length; return length; } } | class MyClass { methodA(name) { var length = name.length; return length; } } |
While stripping out the types from the source allows us to turn AtScript into valid ES6, it does little to assert the validity of the code other than by providing documentation. One way to take advantage of the types is to have the transpiler, e.g. traceur, generate type assertion statements as shown below. Notice that the transpiler is unaware of the semantics of the types, and it leaves all of that information to a third party rtts library. Leaving the assertions to a pluggable library will allow others to experiment with different type system semantics.
AtScript | ES6 |
class MyClass { methodA(name:string):int { var length:int = name.length; return length; } } | import * as rtts from 'rtts'; class MyClass { methodA(name) { rtts.types(name, rtts.string); var length = rtts.type(name.length, rtts.int); return rtts.returnType(length, rtts.int); } } |
Generics allow developers to further constrain the type system of the language and are especially useful when used with data structures such as Maps, Arrays and Promises.
AtScript | ES6 |
class MyClass { methodA(names:List<string>):List<int> { var sizes = []; for(var i = 0; i < names.length; i++) { sizes[i] = names[i].length; } return sizes; } } | import * as rtts from 'rtts'; class MyClass { methodA(names) { rtts.types(names, Array.of(rtts.string)); var sizes = []; for(var i = 0; i < names.length; i++) { sizes[i] = names[i].length; } return rtts.returnType( sizes, Array.of(rtts.int)); } } |
The generic information is fed into the runtime type verification which can be used to further constrain the kind of types which are allowed. For example, it is possible to assert that the array consists of strings which are in turn CSS selectors, something which is difficult to do in most static type systems.
The runtime type system can further be supplemented by writing a static analyzer tool which, with the help of type inference, can identify some of the errors without actually executing the code. The helpfulness of the static tool will be proportional to the amount of types present in the source code, and will complement the runtime assertion.
As mentioned earlier, explicit field declarations are needed to fully describe the type’s structure. This syntax has been proposed in TypeScript, and should be familiar to most developers.
AtScript | ES6 |
class Point { x:int; y:int; } | class MyClass { constructor() { this.x = null; // auto-generated this.y = null; // auto-generated } } |
By specifying the fields in this way, the transpiler can generate the constructor which then pre-creates the fields. By creating the fields in the same order it will allow most virtual machines to optimize their hidden class system, thereby enhancing performance. Forgetting to declare a field will still produce working code, but the static analyzer could generate a warning to notify the developer of the potential mistake.
Annotations are a declarative way of attaching metadata to the code. The simplest form of annotation is the type information itself.
AtScript | ES6 |
class MyClass { constructor(name:string, list:Array) {} } | import * as rtts from 'rtts'; class MyClass { constructor(name, list) { } } MyClass.parameters = [ {is: rtts.string}, {is: Array} ]; |
The parameter type information is retained at runtime and made available to the rest of the system. This is useful for frameworks, such as dependency injection, as shown in the next, more realistic example.
AtScript | ES6 |
class Inject {} class Component { selector:string; constructor({selector:string}) { this.selector = selector; } } class Server {} @Component({selector: 'foo'}) class MyComponent { @Inject() constructor(server:Server) {} } | import * as rtts from 'rtts'; class Inject {} class Component { selector; constructor({selector}) { this.selector = selector; } } class Server {} class MyComponent { constructor(server) {} } MyComponent.parameters = [{is:Server}]; MyComponent.annotate = [ new Component({selector: 'foo'}), new Inject() ]; |
An explicit goal of AtScript is to have the type annotation data available at runtime to be used by projects such as various dependency injection libraries or AngularJS. At runtime a framework needs access to a function's annotations, parameter types (and their annotations) and return types. For a constructor we also need its field metadata including names, types and annotations. The type and meta-data annotations provide important control information for frameworks which would be more difficult and error prone to express imperatively.
AtScript | ES5 |
@Component() class MyApp { server:Server; @Bind('name') name:string; @Event('foo') fooFn:Function; @Inject() constructor(@parent server:Server) {} greet():string {} } | function MyApp() {} MyApp.properties = { 'server': { is: Server }, 'name': { is:string, annotate: [new Bind('name']}, 'fooFn': { is:Function, annotate:[new Event('foo')]} } MyApp.annotate = [ new Component(), new Inject() ]; MyApp.parameters = [ {is:Server, annotate:[parent]} ]; MyApp.prototype.greet = function() {} MyApp.prototype.greet.returns = string; |
NOTE: Mechanisms to limit the amount of runtime type annotations available at production is needed to prevent delivering dead data and bloating the applications. This is outside the scope of this document.
The Angular team started working on AtScript over a year ago in an effort to produce a more maintainable and easier to understand Angular code base. The goal was to also allow applications to be written in AtScript syntax, but AtScript, or ES6 should not be required when writing an angular app, so it was important to keep backwards compatibility with ES5.
Currently we are in the midst of writing Angular v2 on top of the AtScript syntax. Here is an inventory of what is working today:
We would like to add:
The AtScript has been influenced by TypeScript and Dart. Here we would like to discuss why these existing solutions do not meet our needs.
TypeScript
Dart
Both of these languages are solving similar problems. The fundamental difference today is that TypeScript is based on ES5 and AtScript is explicitly based on ES6. Once TypeScript aligns with ES6, we intend to be a strict superset of TypeScript. We are collaborating with the TypeScript team on this effort.
Our plan is to use AtScript for building Angular and optionally let users of Angular leverage AtScript to build their own applications. AtScript is independent of Angular and as such we think it would be useful for other non-Angular projects. Once we prove the usefulness of this approach and get a better handle on the corner cases by building real world applications, we would like to offer it to the standardization bodies. We think it is generally agreed upon that types are something people would like to see in JS. What seems to be under contention is: What do these types mean? AtScript specifically defines syntax, but stays clear of the semantic discussion, therefore we think it would be a good introductory step towards official types in JavaScript.