Ben Newman
(Facebook)
EmpireJS 2014
{ github, twitter, instagram, facebook }.com/benjamn
~
trickif (["a", 2, true].indexOf(x) != -1) { console.log(x + ' was either "a", 2, or true'); }
var recast = require("recast"); var Visitor = recast.Visitor; var types = recast.types; var n = types.namedTypes; var b = types.builders; var ast = recast.parse( '["a", 2, true].indexOf(x) != -1' );
var NotEqualNegativeOneVisitor = Visitor.extend({ });
var NotEqualNegativeOneVisitor = Visitor.extend({ visitBinaryExpression: function(node) { } });
var NotEqualNegativeOneVisitor = Visitor.extend({ visitBinaryExpression: function(node) { var operator = node.operator; } });
var NotEqualNegativeOneVisitor = Visitor.extend({ visitBinaryExpression: function(node) { var operator = node.operator; if (/(!=|==)=?/.test(operator)) { } } });
var NotEqualNegativeOneVisitor = Visitor.extend({ visitBinaryExpression: function(node) { var operator = node.operator; if (/(!=|==)=?/.test(operator)) { if (this.isNegativeOne(node.left)) { node = node.right; } else if (this.isNegativeOne(node.right)) { node = node.left; } } }, isNegativeOne: function(node) { } });
var NotEqualNegativeOneVisitor = Visitor.extend({ visitBinaryExpression: function(node) { var operator = node.operator; if (/(!=|==)=?/.test(operator)) { if (this.isNegativeOne(node.left)) { node = node.right; } else if (this.isNegativeOne(node.right)) { node = node.left; } else return; } }, isNegativeOne: function(node) { } });
var NotEqualNegativeOneVisitor = Visitor.extend({ visitBinaryExpression: function(node) { var operator = node.operator; if (/(!=|==)=?/.test(operator)) { if (this.isNegativeOne(node.left)) { node = node.right; } else if (this.isNegativeOne(node.right)) { node = node.left; } else return; node = b.unaryExpression("~", node); } }, isNegativeOne: function(node) { } });
var NotEqualNegativeOneVisitor = Visitor.extend({ visitBinaryExpression: function(node) { var operator = node.operator; if (/(!=|==)=?/.test(operator)) { if (this.isNegativeOne(node.left)) { node = node.right; } else if (this.isNegativeOne(node.right)) { node = node.left; } else return; node = b.unaryExpression("~", node); if (operator.charAt(0) === "=") { node = b.unaryExpression("!", node); } } }, isNegativeOne: function(node) { } });
var NotEqualNegativeOneVisitor = Visitor.extend({ visitBinaryExpression: function(node) { var operator = node.operator; if (/(!=|==)=?/.test(operator)) { if (this.isNegativeOne(node.left)) { node = node.right; } else if (this.isNegativeOne(node.right)) { node = node.left; } else return; node = b.unaryExpression("~", node); if (operator.charAt(0) === "=") { node = b.unaryExpression("!", node); } return this.genericVisit(node); } }, isNegativeOne: function(node) { } });
var NotEqualNegativeOneVisitor = Visitor.extend({ visitBinaryExpression: function(node) { var operator = node.operator; if (/(!=|==)=?/.test(operator)) { if (this.isNegativeOne(node.left)) { node = node.right; } else if (this.isNegativeOne(node.right)) { node = node.left; } else return; node = b.unaryExpression("~", node); if (operator.charAt(0) === "=") { node = b.unaryExpression("!", node); } return this.genericVisit(node); } }, isNegativeOne: function(node) { return n.UnaryExpression.check(node) && node.operator === "-" && n.Literal.check(node.argument) && node.argument.value === 1; } });
var visitor = new NotEqualNegativeOneVisitor(); ast = visitor.visit(ast); console.log(recast.print(ast).code); // Which prints: ~["a", 2, true].indexOf(x)
var recast = require("recast"); var ast = recast.parse(source); transform(ast); // Anything goes. console.log(recast.print(ast).code);
find ~/www/html/js/lib | \ grep "\.js$" | \ time parallel ~/www/scripts/bin/classify --update 228.03s user 12.25s system 1229% cpu 19.548 total
Why isn't every Python project using Python 3 yet?
Crazy idea: ease into the new language by simulating its most useful features in the current version of JavaScript (ECMAScript 5).
=>
function syntax[3, 1, 10, 28].sort((a, b) => a - b)
var recast = require("recast"); var types = recast.types; var traverse = types.traverse; var n = types.namedTypes; var b = types.builders; var ast = recast.parse( "[3, 1, 10, 28].sort((a, b) => a - b)" );
traverse(ast, function(node) { });
traverse(ast, function(node) { if (n.ArrowFunctionExpression.check(node)) { } });
traverse(ast, function(node) { if (n.ArrowFunctionExpression.check(node)) { var body = node.body; if (node.expression) { node.expression = false; body = b.blockStatement([b.returnStatement(body)]); } } });
traverse(ast, function(node) { if (n.ArrowFunctionExpression.check(node)) { var body = node.body; if (node.expression) { node.expression = false; body = b.blockStatement([b.returnStatement(body)]); } var funExp = b.functionExpression( node.id, node.params, body, node.generator, node.expression ); } });
traverse(ast, function(node) { if (n.ArrowFunctionExpression.check(node)) { var body = node.body; if (node.expression) { node.expression = false; body = b.blockStatement([b.returnStatement(body)]); } var funExp = b.functionExpression( node.id, node.params, body, node.generator, node.expression ); var bindExp = b.callExpression( b.memberExpression(funExp, b.identifier("bind"), false), [b.thisExpression()] ); } });
traverse(ast, function(node) { if (n.ArrowFunctionExpression.check(node)) { var body = node.body; if (node.expression) { node.expression = false; body = b.blockStatement([b.returnStatement(body)]); } var funExp = b.functionExpression( node.id, node.params, body, node.generator, node.expression ); var bindExp = b.callExpression( b.memberExpression(funExp, b.identifier("bind"), false), [b.thisExpression()] ); this.replace(bindExp); } });
console.log(recast.print(ast).code); // Which prints: [3, 1, 10, 28].sort(function(a, b) { return a - b; }.bind(this))
If you already have a build step for static resources, you can be cooking with arrow functions in a matter of minutes!
function *fibonacci(limit) { var a = 0; var b = 1; limit = limit || Infinity; while (a <= limit) { yield a; var next = a + b; a = b; b = next; } }
var g = fibonacci(10); console.log(g.next().value); // 0 console.log(g.next().value); // 1 console.log(g.next().value); // 1 console.log(g.next().value); // 2 console.log(g.next().value); // 3 console.log(g.next().value); // 5 console.log(g.next().value); // 8 console.log(g.next().done); // true
function*
or yield
?
I've been jokingly referring to this side project as "my life's
work" for so long that I'm terrified it might
actually be finished soon.
— Ben Newman (@benjamn)
May 8, 2013
function *fibonacci(limit) { var a = 0; var b = 1; limit = limit || Infinity; while (a <= limit) { yield a; var next = a + b; a = b; b = next; } }
function *fibonacci(limit) { var a, b, next; a = 0; b = 1; limit = limit || Infinity; while (a <= limit) { yield a; next = a + b; a = b; b = next; } }
function fibonacci(limit) { var a, b, next; return function*() { a = 0; b = 1; limit = limit || Infinity; while (a <= limit) { yield a; next = a + b; a = b; b = next; } }; }
function fibonacci(limit) { var a, b, next; return function(context) { while (1) switch (context.next) { } }; }
function fibonacci(limit) { var a, b, next; return function(context) { while (1) switch (context.next) { case 0: a = 0; b = 1; limit = limit || Infinity; } }; }
function fibonacci(limit) { var a, b, next; return function(context) { while (1) switch (context.next) { case 0: a = 0; b = 1; limit = limit || Infinity; case 3: if (!(a <= limit)) { context.next = 11; break; } } }; }
function fibonacci(limit) { var a, b, next; return function(context) { while (1) switch (context.next) { case 0: a = 0; b = 1; limit = limit || Infinity; case 3: if (!(a <= limit)) { context.next = 11; break; } case 11: case "end": return context.stop(); } }; }
function fibonacci(limit) { var a, b, next; return function(context) { while (1) switch (context.next) { case 0: a = 0; b = 1; limit = limit || Infinity; case 3: if (!(a <= limit)) { context.next = 11; break; } context.next = 6; return a; case 11: case "end": return context.stop(); } }; }
function fibonacci(limit) { var a, b, next; return function(context) { while (1) switch (context.next) { case 0: a = 0; b = 1; limit = limit || Infinity; case 3: if (!(a <= limit)) { context.next = 11; break; } context.next = 6; return a; case 6: next = a + b; a = b; b = next; case 11: case "end": return context.stop(); } }; }
function fibonacci(limit) { var a, b, next; return function(context) { while (1) switch (context.next) { case 0: a = 0; b = 1; limit = limit || Infinity; case 3: if (!(a <= limit)) { context.next = 11; break; } context.next = 6; return a; case 6: next = a + b; a = b; b = next; context.next = 3; break; case 11: case "end": return context.stop(); } }; }
function fibonacci(limit) { var a, b, next; return wrapGenerator(function(context) { while (1) switch (context.next) { case 0: a = 0; b = 1; limit = limit || Infinity; case 3: if (!(a <= limit)) { context.next = 11; break; } context.next = 6; return a; case 6: next = a + b; a = b; b = next; context.next = 3; break; case 11: case "end": return context.stop(); } }, fibonacci, this); }
var fibonacci = function fibonacci(limit) { var a, b, next; return wrapGenerator(function(context) { while (1) switch (context.next) { case 0: a = 0; b = 1; limit = limit || Infinity; case 3: if (!(a <= limit)) { context.next = 11; break; } context.next = 6; return a; case 6: next = a + b; a = b; b = next; context.next = 3; break; case 11: case "end": return context.stop(); } }, fibonacci, this); };
async
functionsasync function chainAnimations(elem, animations) { var ret = null; for (var i = 0; i < animations.length; ++i) ret = await animations[i](elem); return ret; } chainAnimations($("#card"), [ flip(100), slide(200), ... ]).then(function() { console.log("all done!"); });
function chainAnimations(elem, animations) { var ret, i; return wrapGenerator.async(function(context) { while (1) switch (context.next) { case 0: ret = null; i = 0; case 2: if (!(i < animations.length)) { context.next = 9; break; } context.next = 5; return animations[i](elem); case 5: ret = context.sent; case 6: ++i; context.next = 2; break; case 9: return context.abrupt("return", ret); case 10: case "end": return context.stop(); } }, null, this); }
wrapGenerator.async = function(innerFn, self, tryList) { return new Promise(function(resolve, reject) { var generator = wrapGenerator(innerFn, self, tryList); var callNext = step.bind(generator.next); var callThrow = step.bind(generator.throw); function step(arg) { try { var info = this(arg); var value = info.value; } catch (error) { return reject(error); } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); };
async
functions and
the await
keyword has already been fully
implemented in
this pull request.
async
functions and the await
keyword?
Incremental transpilation is the key to avoiding the Python 3 trap.
It's how we bring about the future without choking on it.
It's how we know, sooner rather than later, how great the future will be.
It is, quite literally, linguistic time travel.
Not everything can be transpiled.
AST transforms don't always play well together.
Niceties like source maps become an absolute necessity when debugging generated code.
The language specification can change out from under you.
It's all going to be open-source.
We're only just getting started.
You are more than welcome to help.
github.com/{ benjamn/empirejs-talk, facebook/jstransform, benjamn/recast, benjamn/ast-types, facebook/regenerator } code.facebook.com/projects