On Github benjamn / jsconf-2014
Ben Newman (Facebook)JSConf 2014
{ github, twitter, instagram, facebook }.com/benjamn
Congratulations! You're not done.
Not even if you're right.
Not even if everyone agrees that you're right.
Code just left there without warranty of any kind, either expressed or implied, in the vague hope that other people face needs that are similar to the ones that motivated the author.
Often “better” just means “better tailored to my particular use case.”
In which case there's no mystery why the evangelical message went nowhere.
And it can't involve throwing away all of your code and starting over from scratch, as tempting as that might be.
As the evangelist, you have a responsibility to yourself, to your ideas, and to the people you want to help, to devise and execute an incremental migration plan.
Other people won't be willing to buy in unless you justify the effort and short-term increase in complexity.
Why isn't every Python project using Python 3 yet?
It has that 2to3 script.
The maintainers have realistic expectations.
Biggest barrier: changes in unicode semantics.
Attempting to solve a problem once and for all typically requires adapting many different, somewhat-functional past solutions.
Crazy idea: ease into the new language by simulating its most useful features in the current version of JavaScript (ECMAScript 5).
The great virtue of this idea is that it makes sense at every stage.
Even if it never ultimately connects with the next version of the language, or takes a long time getting there, we still benefit.
[3, 1, 10, 28].sort((a, b) => a - b)
[3, 1, 10, 28].sort(function(a, b) {
return a - b;
}.bind(this))
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!
var recast = require("recast");
var ast = recast.parse(source);
transform(ast); // Anything goes.
console.log(recast.print(ast).code);
Instead of simply pretty-printing the whole tree, recast.print tries to recyle the original source code wherever possible.
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
function *permutations(list) {
if (list.length < 2) {
yield list;
} else {
var first = list.slice(0, 1);
var ps = permutations(list.slice(1));
for (var p of ps) {
for (var i = 0; i < list.length; ++i) {
yield Array.prototype.concat.call(
p.slice(0, i), // prefix
first, // middle
p.slice(i) // suffix
);
}
}
}
}
var g = permutations([1, 3, 2]);
g.next().value; // [1, 3, 2]
g.next().value; // [3, 1, 2]
g.next().value; // [3, 2, 1]
g.next().value; // [1, 2, 3]
g.next().value; // [2, 1, 3]
g.next().value; // [2, 3, 1]
g.next().done; // true
function *permutations(list) {
if (list.length < 2) {
yield list;
} else {
var first = list.slice(0, 1);
var ps = permutations(list.slice(1));
for (var p of ps) {
for (var i = 0; i < list.length; ++i) {
yield Array.prototype.concat.call(
p.slice(0, i), // prefix
first, // middle
p.slice(i) // suffix
);
}
}
}
}
var g = permutations([1, 3, 2]);
var count = 0;
for (var p of g) {
console.log(p);
++count;
}
console.log(count); // 6
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, 2013function permutations(list) {
if (list.length < 2) {
yield list;
} else {
var first = list.slice(0, 1);
var ps = permutations(list.slice(1));
for (var p of ps) {
for (var i = 0; i < list.length; ++i) {
yield Array.prototype.concat.call(
p.slice(0, i), // prefix
first, // middle
p.slice(i) // suffix
);
}
}
}
}
function permutations(list) {
var first, ps, p, i;
if (list.length < 2) {
yield list;
} else {
first = list.slice(0, 1);
ps = permutations(list.slice(1));
for (p of ps) {
for (i = 0; i < list.length; ++i) {
yield Array.prototype.concat.call(
p.slice(0, i), // prefix
first, // middle
p.slice(i) // suffix
);
}
}
}
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
if (list.length < 2) {
yield list;
} else {
first = list.slice(0, 1);
ps = permutations(list.slice(1));
for (p of ps) {
for (i = 0; i < list.length; ++i) {
yield Array.prototype.concat.call(
p.slice(0, i), // prefix
first, // middle
p.slice(i) // suffix
);
}
}
}
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
if (list.length < 2) {
yield list;
} else {
first = list.slice(0, 1);
ps = permutations(list.slice(1));
t$0 = wrapGenerator.values(ps);
while (!(t$1 = t$0.next()).done) {
p = t$1.value;
for (i = 0; i < list.length; ++i) {
yield Array.prototype.concat.call(
p.slice(0, i), // prefix
first, // middle
p.slice(i) // suffix
);
}
}
}
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function*() {
if (list.length < 2) {
yield list;
} else {
first = list.slice(0, 1);
ps = permutations(list.slice(1));
t$0 = wrapGenerator.values(ps);
while (!(t$1 = t$0.next()).done) {
p = t$1.value;
for (i = 0; i < list.length; ++i) {
yield Array.prototype.concat.call(
p.slice(0, i), // prefix
first, // middle
p.slice(i) // suffix
);
}
}
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function(context) {
while (1) switch (context.next) {
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function(context) {
while (1) switch (context.next) {
case 0:
if (!(list.length < 2)) {
context.next = 5;
break;
}
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function(context) {
while (1) switch (context.next) {
case 0:
if (!(list.length < 2)) {
context.next = 5;
break;
}
context.next = 3;
return list;
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function(context) {
while (1) switch (context.next) {
case 0:
if (!(list.length < 2)) {
context.next = 5;
break;
}
context.next = 3;
return list;
case 3:
context.next = 19;
break;
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function(context) {
while (1) switch (context.next) {
case 0:
if (!(list.length < 2)) {
context.next = 5;
break;
}
context.next = 3;
return list;
case 3:
context.next = 19;
break;
case 19:
case "end":
return context.stop();
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function(context) {
while (1) switch (context.next) {
case 0:
if (!(list.length < 2)) {
context.next = 5;
break;
}
context.next = 3;
return list;
case 3:
context.next = 19;
break;
case 5:
first = list.slice(0, 1);
ps = permutations(list.slice(1));
t$0 = wrapGenerator.values(ps);
case 19:
case "end":
return context.stop();
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function(context) {
while (1) switch (context.next) {
case 0:
if (!(list.length < 2)) {
context.next = 5;
break;
}
context.next = 3;
return list;
case 3:
context.next = 19;
break;
case 5:
first = list.slice(0, 1);
ps = permutations(list.slice(1));
t$0 = wrapGenerator.values(ps);
case 8:
if ((t$1 = t$0.next()).done) {
context.next = 19;
break;
}
p = t$1.value;
case 19:
case "end":
return context.stop();
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function(context) {
while (1) switch (context.next) {
case 0:
if (!(list.length < 2)) {
context.next = 5;
break;
}
context.next = 3;
return list;
case 3:
context.next = 19;
break;
case 5:
first = list.slice(0, 1);
ps = permutations(list.slice(1));
t$0 = wrapGenerator.values(ps);
case 8:
if ((t$1 = t$0.next()).done) {
context.next = 19;
break;
}
p = t$1.value;
i = 0;
case 11:
if (!(i < list.length)) {
context.next = 17;
break;
}
case 19:
case "end":
return context.stop();
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function(context) {
while (1) switch (context.next) {
case 0:
if (!(list.length < 2)) {
context.next = 5;
break;
}
context.next = 3;
return list;
case 3:
context.next = 19;
break;
case 5:
first = list.slice(0, 1);
ps = permutations(list.slice(1));
t$0 = wrapGenerator.values(ps);
case 8:
if ((t$1 = t$0.next()).done) {
context.next = 19;
break;
}
p = t$1.value;
i = 0;
case 11:
if (!(i < list.length)) {
context.next = 17;
break;
}
context.next = 14;
return Array.prototype.concat.call(
p.slice(0, i), // prefix
first, // middle
p.slice(i) // suffix
);
case 19:
case "end":
return context.stop();
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function(context) {
while (1) switch (context.next) {
case 0:
if (!(list.length < 2)) {
context.next = 5;
break;
}
context.next = 3;
return list;
case 3:
context.next = 19;
break;
case 5:
first = list.slice(0, 1);
ps = permutations(list.slice(1));
t$0 = wrapGenerator.values(ps);
case 8:
if ((t$1 = t$0.next()).done) {
context.next = 19;
break;
}
p = t$1.value;
i = 0;
case 11:
if (!(i < list.length)) {
context.next = 17;
break;
}
context.next = 14;
return Array.prototype.concat.call(
p.slice(0, i), // prefix
first, // middle
p.slice(i) // suffix
);
case 14:
++i;
context.next = 11;
break;
case 19:
case "end":
return context.stop();
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return function(context) {
while (1) switch (context.next) {
case 0:
if (!(list.length < 2)) {
context.next = 5;
break;
}
context.next = 3;
return list;
case 3:
context.next = 19;
break;
case 5:
first = list.slice(0, 1);
ps = permutations(list.slice(1));
t$0 = wrapGenerator.values(ps);
case 8:
if ((t$1 = t$0.next()).done) {
context.next = 19;
break;
}
p = t$1.value;
i = 0;
case 11:
if (!(i < list.length)) {
context.next = 17;
break;
}
context.next = 14;
return Array.prototype.concat.call(
p.slice(0, i), // prefix
first, // middle
p.slice(i) // suffix
);
case 14:
++i;
context.next = 11;
break;
case 17:
context.next = 8;
break;
case 19:
case "end":
return context.stop();
}
};
}
function permutations(list) {
var first, ps, p, t$0, t$1, i;
return wrapGenerator(function(context) {
while (1) switch (context.next) {
case 0:
if (!(list.length < 2)) {
context.next = 5;
break;
}
context.next = 3;
return list;
case 3:
context.next = 19;
break;
case 5:
first = list.slice(0, 1);
ps = permutations(list.slice(1));
t$0 = wrapGenerator.values(ps);
case 8:
if ((t$1 = t$0.next()).done) {
context.next = 19;
break;
}
p = t$1.value;
i = 0;
case 11:
if (!(i < list.length)) {
context.next = 17;
break;
}
context.next = 14;
return Array.prototype.concat.call(
p.slice(0, i), // prefix
first, // middle
p.slice(i) // suffix
);
case 14:
++i;
context.next = 11;
break;
case 17:
context.next = 8;
break;
case 19:
case "end":
return context.stop();
}
}, permutations, this);
}
case "ForStatement":
case "ForStatement":
var head = loc();
var update = loc();
var after = loc();
case "ForStatement":
var head = loc();
var update = loc();
var after = loc();
if (stmt.init) {
self.explode(path.get("init"), true);
}
case "ForStatement":
var head = loc();
var update = loc();
var after = loc();
if (stmt.init) {
self.explode(path.get("init"), true);
}
self.mark(head);
case "ForStatement":
var head = loc();
var update = loc();
var after = loc();
if (stmt.init) {
self.explode(path.get("init"), true);
}
self.mark(head);
if (stmt.test) {
self.jumpIfNot(self.explodeExpression(path.get("test")), after);
}
case "ForStatement":
var head = loc();
var update = loc();
var after = loc();
if (stmt.init) {
self.explode(path.get("init"), true);
}
self.mark(head);
if (stmt.test) {
self.jumpIfNot(self.explodeExpression(path.get("test")), after);
}
self.leapManager.withEntry(
new leap.LoopEntry(after, update, labelId),
function() { self.explodeStatement(path.get("body")); }
);
case "ForStatement":
var head = loc();
var update = loc();
var after = loc();
if (stmt.init) {
self.explode(path.get("init"), true);
}
self.mark(head);
if (stmt.test) {
self.jumpIfNot(self.explodeExpression(path.get("test")), after);
}
self.leapManager.withEntry(
new leap.LoopEntry(after, update, labelId),
function() { self.explodeStatement(path.get("body")); }
);
self.mark(update);
case "ForStatement":
var head = loc();
var update = loc();
var after = loc();
if (stmt.init) {
self.explode(path.get("init"), true);
}
self.mark(head);
if (stmt.test) {
self.jumpIfNot(self.explodeExpression(path.get("test")), after);
}
self.leapManager.withEntry(
new leap.LoopEntry(after, update, labelId),
function() { self.explodeStatement(path.get("body")); }
);
self.mark(update);
if (stmt.update) {
self.explode(path.get("update"), true);
}
case "ForStatement":
var head = loc();
var update = loc();
var after = loc();
if (stmt.init) {
self.explode(path.get("init"), true);
}
self.mark(head);
if (stmt.test) {
self.jumpIfNot(self.explodeExpression(path.get("test")), after);
}
self.leapManager.withEntry(
new leap.LoopEntry(after, update, labelId),
function() { self.explodeStatement(path.get("body")); }
);
self.mark(update);
if (stmt.update) {
self.explode(path.get("update"), true);
}
self.jump(head);
case "ForStatement":
var head = loc();
var update = loc();
var after = loc();
if (stmt.init) {
self.explode(path.get("init"), true);
}
self.mark(head);
if (stmt.test) {
self.jumpIfNot(self.explodeExpression(path.get("test")), after);
}
self.leapManager.withEntry(
new leap.LoopEntry(after, update, labelId),
function() { self.explodeStatement(path.get("body")); }
);
self.mark(update);
if (stmt.update) {
self.explode(path.get("update"), true);
}
self.jump(head);
self.mark(after);
case "ForStatement":
var head = loc();
var update = loc();
var after = loc();
if (stmt.init) {
self.explode(path.get("init"), true);
}
self.mark(head);
if (stmt.test) {
self.jumpIfNot(self.explodeExpression(path.get("test")), after);
}
self.leapManager.withEntry(
new leap.LoopEntry(after, update, labelId),
function() { self.explodeStatement(path.get("body")); }
);
self.mark(update);
if (stmt.update) {
self.explode(path.get("update"), true);
}
self.jump(head);
self.mark(after);
break;
// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
return { type: "Literal", value: -1 };
}
// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
return require("recast").types.builders.literal(-1);
}
// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
return b.literal(-1);
}
// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
return b.literal(-1);
}
// Sets the exact value of the given location to the offset of the next
// Statement emitted.
Emitter.prototype.mark = function(loc) {
assert.strictEqual(loc.type, "Literal");
var index = this.listing.length;
if (loc.value === -1) {
loc.value = index;
} else {
// Locations can be marked redundantly, but their values cannot change
// once set the first time.
assert.strictEqual(loc.value, index);
}
this.marked[index] = true;
return loc;
};
// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
return b.literal(-1);
}
// Sets the exact value of the given location to the offset of the next
// Statement emitted.
Emitter.prototype.mark = function(loc) {
require("recast").types.namedTypes.Literal.assert(loc);
var index = this.listing.length;
if (loc.value === -1) {
loc.value = index;
} else {
// Locations can be marked redundantly, but their values cannot change
// once set the first time.
assert.strictEqual(loc.value, index);
}
this.marked[index] = true;
return loc;
};
// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
return b.literal(-1);
}
// Sets the exact value of the given location to the offset of the next
// Statement emitted.
Emitter.prototype.mark = function(loc) {
n.Literal.assert(loc);
var index = this.listing.length;
if (loc.value === -1) {
loc.value = index;
} else {
// Locations can be marked redundantly, but their values cannot change
// once set the first time.
assert.strictEqual(loc.value, in