JavaScript: Say it like you mean it

JavaScript:
Say it like you mean it

Welcome. Traceur project's goal is to evolve JavaScript to make writing web applications more fun.

Use the arrow keys to advance slides. Press N to toggle speaker notes. If the notes don't fit on screen, zoom out with Cmd/Ctrl - (minus).

(We took a few implementation shortcuts to build this presentation quickly, so it only works in Google Chrome at the moment.)

Web is open and empowering. People have built amazing Web Apps.

You have to be a gluton for punishment.

Its not just a programmer hacking a web site. Its 50 strong team engineering gmail frontend.

Creative in attempts to build large apps. Notably GWT.

"Language design is library design and library design is language design"

What is needed?

We need to improve the web platform to keep the web vibrant.

Adding features to JS that enables large software development.

Components:

Classes

Inheritance

Differentiating factors between large applications and small is the need for components.

Reusable pieces of software with well defined boundaries.

Classes are small units of reusable software.

We're going to look at just one aspect of classes - inheritance.

Prototypes & Inheritance

function Actor() { ... }
Actor.prototype.moveTo = function (pos) { ... };
function PacMan() { }
PacMan.prototype = new Actor();
PacMan.prototype.eat = function() { ... };
var pac = new PacMan();
assertTrue(pac instanceof Actor);

JS has prototypal inheritance - let's take a look.

This is a class, or more specifically a constructor function. The word "function" doesn’t mean what you think it means… unless you know that this is being written in a particular style.

This is how you do inheritance. Set the prototype of the derived to be an instance of the base.

Which allows you to create instances of the derived which are also instances of the base. Works great... ... Except when your base constructor has side effects!

So let's look at what really happens in practice ...

Inheritance

function Actor() { ... }
Actor.prototype.moveTo = function (pos) { ... };

function PacMan() { }
PacMan.prototype = (function() {
    function tempCtor() {};
    tempCtor.prototype = Actor.prototype;
    var proto = new tempCtor();
    proto.constructor = PacMan;
    return proto;
  })();

PacMan.prototype.eat = function() { ... };

So let's look at how prototypal inheritance is really done.

Now that says - inheritance to me.

Just to be clear. I am not making this up - this is actual best practice.

Inheritance - Libraries

goog.inherits(Actor, PacMan);
PacMan = Class.create(Actor, ... );
PacMan = Actor.extend(...);

So the library authors decided to fix the problem once and for all.

Once for closure ...

Once for prototype ...

... and Once for base2. All great solutions. None of them interoperable.

Classes & Inheritance

class Actor {
  new() { ... }
  moveTo: function (pos) { ... }
}

class PacMan : Actor {
  new() { ... }
  eat: function() { ... }
}

Say what you mean... Note that you can see that it’s actually a class just by looking at it. This looks easy, but its actually really, really hard. Good designs look easy. There's a lot more to classes than just inheritance.

Components:

Modules

Code Loading

Another part of reusable components is code distribution.

Modules & Code Loading

goog.provide('goog.ui.tree.TreeControl');

goog.require('goog.events.EventType');
goog.require('goog.events.FocusHandler');
goog.require('goog.events.KeyHandler');
goog.require('goog.ui.tree.BaseNode');
goog.require('goog.ui.tree.TreeNode');
goog.require('goog.ui.tree.TypeAhead');

goog.ui.tree.TreeControl = ...;
This is how we do "modules" today. Requiring a module pulls in everything from it, there’s no automatic local aliasing, and no way to prevent polluting the global scope.

Modules & Code Loading

define("dijit/Tree",
  ["dojo",
   "dijit",
   "text!dijit/templates/TreeNode.html",
   "text!dijit/templates/Tree.html",
   "dijit/_Widget",
   "dijit/_Container",
   "dijit/_Contained"],
    function(dojo, dijit) {
      dojo.declare("dijit.Tree", ... );
      return dijit.Tree;
    });
The “modern” version doesn’t even look like a “module”. JS really needs a module system of its own.

Modules & Code Loading

// goog/ui/tree/TreeControl.js
module Events = require('goog/events');
import Events.{EventType, FocusHandler, KeyHandler};

module Tree = require('goog/ui/tree');
import Tree.{BaseNode, TreeNode, TypeAhead};

export TreeControl = ...;

Files are modules when they’re included as modules, and they can contain many exports. The sync/async tension in requires is handled by the language runtime and doesn’t necessarily need extra magic or a compiler to make it perform adequately in most cases.

The syntax for this isn’t final, and we’re working inside of TC39 to improve it, but we like the semantics of this new system.

Components:

Scoped Object Extensions

Monkey Patching

  Object.extend(Array.prototype, {
    clear:     clear,
    compact:   compact,
    flatten:   flatten,
    reverse:   reverse,
    uniq:      uniq,
    intersect: intersect,
    clone:     clone
  });
  var dots = allDots.clone();
  var dots = goog.array.clone(allDots);

Monkey patching is used extensively in real libraries. This example comes from the prototype library. Note they monkey patch by using a monkey patch.

This is handy because you can add helpers that can be called quite naturally.

But monkey patching in large applications leads to subtle conflicts between libraries. Other libraries go to great pains to not monkey patch. This avoids conflicts between libraries, but has a singificant cost to the usability of the API.

Scoped Object Extensions

  module Prototype {
    export extension Extensions = Array.prototype {
      clone: function () { ... }
    }

    ... [a, b, c].clone() ...
  }
  assertUndefined([].clone);
  import Prototype.Extensions;
  var dots = allDots.clone();

Extensions are defined as part of a module. And are available inside that module.

Outside of their defining module, extensions are not visible.

But they can be imported across module boundaries.

Productivity:

Async Programming

Callbacks

function animate(element, callback) {
  var left = -1;
  function next() {
    left++;
    if (left < 350) {
      element.style.marginLeft = left + 'px';
      window.setTimeout(next, 5);
    } else {
      callback();
    }
  }
  next();
}
animate(document.getElementById('now'), function() { alert('done!'); });

JS is embedded in an environment where blocking calls are bad. So all long running APIs use callbacks. Examples include setTimeout and XHRs.

The callback infects your API in a non-composable way.

Deferred Pattern

function deferTimeout(ms) {
  var deferred = new Deferred();
  window.setTimeout(function() { deferred.callback(); }, ms);
  return deferred;
}
function animate(element) {
  var left = -1, deferred = new Deferred();
  function next() {
    if (++left < 350) {
      element.style.marginLeft = left + 'px';
      deferTimeout(5).then(next);
    } else {
      deferred.callback();
    }
  }
  next(); return deferred;
}
animate(document.getElementById('now')).then(function() { alert('done!'); });

Library authors invented the Deferred pattern to alleviate the composability issue. It moves the callback from the argument list to the return value. Since the return value is first class it enables composability.

The implementation is still twisted into CPS however.

Deferred Functions

function deferTimeout(ms) {
  var deferred = new Deferred();
  window.setTimeout(function() { deferred.callback(); }, ms);
  return deferred;
}
function animate(element) {
  for (var left = 0; left < 350; left++) {
    element.style.marginLeft = left + 'px';
    await deferTimeout(5);
  }
}
animate(document.getElementById('now')).then(function() { alert('Traceur!'); });

Deferred functions allow writing callback heavy code in a natural linear style. Let the compiler do the CPS transform for you.

It takes some getting used to.

Is that it?

Features

classes and traits modules
scoped monkey patching iterators/generators
async/callbacks DOM constructors
lightweight function syntax proper tailcalls
type system block scoped binding
proxies weak maps
destructuring assignment rest parameters
spread operator default parameter values
binary data/typed arrays standard library
The first set of features. We still don't know what else we'll need.

When can I use this stuff?

Now!


      
var done = false; function animate(element) { for (var left = 0; left < 350; left++) { element.style.marginLeft = left + 'px'; await deferTimeout(5); } } function animateicon() { while (!done) { advanceIcon(); await deferTimeout(600); } } animate(document.getElementById('now')).then(function() { done = true; alert('Traceur!'); }); animateicon();

Traceur

http://code.google.com/p/traceur-compiler

http://goo.gl/zEONC

http://goo.gl/XqMen

You can find more information on the Traceur Google Code site.