Keeping your code in Check
Michael Bolin
Make it hard to do the wrong thing.
Static typing benefits
Static typing myths
Classes of type systems
Dynamic typing
Languages: JavaScript, Python
function doQuack(duck) {
if (duck && duck.quack) duck.quack();
}
Dynamic typing
Languages: JavaScript, Python
function doQuack(duck) {
if (duck && duck.quack) duck.quack();
}
var duck1 = new Duck();
var duck2 = {
quack: function() {console.log('quack');}
};
var duck3 = new String('duck');
duck3.quack = duck2.quack;
Explicit typing
Languages: Java, C
String name = "mloc.js";
int year = 2013;
Duck duck = new Duck();
public void doQuack(Duck duck) {
duck.quack();
}
Implicit typing
Languages: OCaml, Haskell
$ let name = "mloc.js";;
Implicit typing
Languages: OCaml, Haskell
$ let name = "mloc.js";;
val name : string = "mloc.js"
Implicit typing
Languages: OCaml, Haskell
$ let name = "mloc.js";;
val name : string = "mloc.js"
$ let year = 2013;;
val year : int = 2013
Implicit typing
Languages: OCaml, Haskell
$ let name = "mloc.js";;
val name : string = "mloc.js"
$ let year = 2013;;
val year : int = 2013
$ let conf = (name, year);;
val conf : string * int = ("mloc.js", 2013)
Implicit typing: mind === blown
$ let print_conf c =
let (name, year) = c in
sprintf "%s is in %d" name year
;;
Implicit typing: mind === blown
$ let print_conf c =
let (name, year) = c in
sprintf "%s is in %d" name year
;;
val print_conf : string * int -> string = <fun>
Implicit typing: mind === blown
$ let print_conf c =
let (name, year) = c in
sprintf "%s is in %d" name year
;;
val print_conf : string * int -> string = <fun>
$ print_conf conf;;
- : string = "mloc.js is in 2013"
Implicit typing: mind === blown
$ let print_conf c =
let (name, year) = c in
sprintf "%s is in %d" name year
;;
val print_conf : string * int -> string = <fun>
$ print_conf conf;;
- : string = "mloc.js is in 2013"
$ print_conf ("mloc.js", "2013");;
Error: This expression has type string
but an expression was expected of type int
Algebraic data types
Only create values for valid states:
type bool = true | false;;
Algebraic data types
Only create values for valid states:
type bool = true | false;;
type checkBoxState = Checked
| Unchecked
| Indeterminate;;
Algebraic data types
Only create values for valid states:
type bool = true | false;;
type checkBoxState = Checked
| Unchecked
| Indeterminate;;
As compared to using two booleans:
boolean checked;
boolean indeterminate;
Algebraic data types:
Never encounter an NPE again
Think: enums with satellite data.
Imagine getElementById returns this type:
type optionalElement = Some of Element.t
| None;;
Algebraic data types:
Never encounter an NPE again
Think: enums with satellite data.
Imagine getElementById returns this type:
type optionalElement = Some of Element.t
| None;;
let getElementById_exn id =
match getElementById id with
| Some e -> e
| None -> raise Not_found
;;
val getElementById_exn : string -> Element.t
Optional Typing
Google Closure
Pure JavaScript with annotations.
TypeScript
Superset of JavaScript with annotations.
Dart
New language designed to compile to JavaScript.
Optional Typing: Google Closure
/**
* @param {!Duck} duck
* @return {string}
*/
function doQuack(duck) {
duck.quack();
return duck.name();
}
Optional Typing: Google Closure
/**
* @param {!Duck} duck
* @return {string}
*/
function doQuack(duck) {
duck.quack();
return duck.name();
}
var name = 'mloc.js';
doQuack(name); // type mismatch
doQuack(null); // type mismatch
name = 2013; // OK
Optional Typing: Google Closure
function doQuack(duck) {
duck.quack();
return duck.name();
}
var name = 'mloc.js';
doQuack(name); // OK
doQuack(null); // OK
name = 2013; // OK
Optional Typing: TypeScript
function doQuack(duck : Duck) : string {
duck.quack();
return duck.name();
}
Optional Typing: TypeScript
function doQuack(duck : Duck) : string {
duck.quack();
return duck.name();
}
var name = 'mloc.js';
var year = 2013;
var duck = new Duck();
doQuack(name); // signature mismatch
name = doQuack(null); // OK
name = year; // cannot convert
Optional Typing: TypeScript
function doQuack(duck) {
duck.quack();
return duck.name();
}
var name = 'mloc.js';
var year = 2013;
var duck = new Duck();
doQuack(name); // OK
name = doQuack(null); // OK
name = year; // cannot convert
Consider the following API
function geocode(address, callback) {
// ...
}
geocode() API in Google Closure
/**
* @typedef {{lat:number, lng:number}}
*/
var LatLng;
/**
* @param {string} address
* @param {function(?LatLng)} callback
*/
function geocode(address, callback) {
// ...
}
geocode() API in TypeScript
interface LatLng {
lat : number;
lng : number;
}
function geocode(
address : string,
callback : (latLng : LatLng) => void) {
// ...
}
Optimizations with Google Closure
Optimizations with Google Closure:
Property Renaming
/* Library code. */
Bag.prototype.add = function(e) {
this.elements.push(e);
};
Optimizations with Google Closure:
Property Renaming
/* Library code. */
Bag.prototype.add = function(e) {
this.elements.push(e);
};
/* Node JS API */
domain.create().add(emitter);
Optimizations with Google Closure:
Property Renaming
/* Library code. */
Bag.prototype.add = function(e) {
this.elements.push(e);
};
/* Node JS API */
domain.create().add(emitter);
/* Can we minify this? */
obj.add(arg);
Optimizations with Google Closure:
Property Renaming
/* Library code AFTER PROPERTY RENAMING. */
Bag.prototype.a = function(e) {
// elements has been renamed to b.
this.b.push(e);
};
Optimizations with Google Closure:
Property Renaming
/* Library code AFTER PROPERTY RENAMING. */
Bag.prototype.a = function(e) {
// elements has been renamed to b.
this.b.push(e);
};
/** @param {!Bag} obj */
var add42ToBag = function(obj) {
// was obj.add(42);
obj.a(42);
};
Optimizations with Google Closure:
Inlining
/* Library code AFTER PROPERTY RENAMING. */
Bag.prototype.a = function(e) {
this.b.push(e);
};
Optimizations with Google Closure:
Inlining
/* Library code AFTER PROPERTY RENAMING. */
Bag.prototype.a = function(e) {
this.b.push(e);
};
/* Business logic AFTER INLINING. */
/** @param {!Bag} obj */
var add42ToBag = function(obj) {
// was obj.a(42);
obj.b.push(42);
};
Optimizations with Google Closure:
Dead Code Elimination
Constant propagation:
if (goog.userAgent.IE) {� // IE-specific-workaround�}
Optimizations with Google Closure:
Dead Code Elimination
Constant propagation:
if (goog.userAgent.IE) {� // IE-specific-workaround�}
Unused library functions:
dom.createNode = function() { /*...*/ };
dom.createElement = function() { /*...*/ };
dom.createFragment = function() { /*...*/ };
Optimizations with Google Closure:
Modules
A B C D E
A
B
C
D
E
Optimizations with Google Closure:
Effects on code size
4380KB | Original JavaScript |
709KB | Closure Compiler |
215KB | Module Partitioning |
73KB | gzip |
Conclusion
Static typing
Thanks!