javascript uncommon programming
TRANSCRIPT
提升JavaScript生产力的“异样”编程手段
赵劼 - 2011.7
关于我• 赵劼 / 老赵 / Jeffrey Zhao / 赵姐夫
• 日写代码三百行,不辞长作程序员• 博客:http://blog.zhaojie.me/
• 微博:@老赵
• F#, JavaScript, Scala, C#, Python, .NET, Mono...
• 痛恨Java语言
请用您熟悉的语言实现以下功能
构建书籍索引
// C#List<string> keywords = ...;var result = keywords .GroupBy(k => k[0].ToUpper()) .ToDictionary( g => g.Key, g => g.OrderBy(k => k).ToList());
// F#let keywords = ...let result = keywords |> Seq.groupBy (fun k -> k.[0].ToUpper()) |> Seq.map (fun (k, v) -> (k, v |> Seq.sort)) |> Map.ofSeq
# Rubykeywords = ...result = keywords \ .group_by { |k| k[0,1].upcase } \ .each { |key, value| value.sort! }
// JavaScript(基于类库扩展)var keywords = ...;var result = keywords .groupBy(function (k) { return k[0].toUpperCase(); }) .each(function (key, value) { value.sort(); });
JavaList<String> keywords = ...;Map<Character, List<String>> result = new HashMap<...>();
for (String k: keywords) { char firstChar = k.charAt(0); if (!result.containsKey(firstChar)) { result.put(firstChar, new ArrayList<String>()); } result.get(firstChar).add(k);}
for (List<String> list: result.values()) { Collections.sort(list);}
东施效颦*
* 类似C#的API扩展
Map<Character, List<String>> result = keyword .groupBy( new F<String, Character> { public Character f(String s) { return s.charAt(0); } }) .toMap( new F<Grouping<Character, String, Character> { public Charactor f(Grouping<Char, String> g) { return g.getKey(); } }, new F<Grouping<Character, String>, List<String>> { public List<String> f(Grouping<Character, String> g) { return g .orderBy( new F<String, String> { public String f(String s) { return s; } }) .toList(); } });
语言特性
• 影响编程思维• 决定编程方式• 限制开发效率
Java丑在哪里?
• 命令式编程(怎么做)• 复杂的匿名类型语法• 强制写清所有类型名• 无法扩展既有类型
现代语言常见特性
• 声明式编程(做什么)• 易用的Lambda表达式语法
• 类型推断(对于静态语言)• 无侵入的类型扩展
JavaScript常用特性• 一等公民函数• 作用域,闭包• prototype扩展
• 动态this引用
• apply,call,arguments,constructor,callee
JavaScript Ninja已经语言实践推向了极致
要进一步提高生产力,必须从语言本身入手。
例1:迭代生成器
var fib = function () { var state = 0, last0, last1;
return { moveNext: function () { if (state == 0) { // 第一项 this.current = 0; state = 1; } else if (state == 1) { // 第二项 this.current = 1; last1 = 0; state = 2; } else { last0 = last1; last1 = this.current; this.current = last0 + last1; } return true; } }}
var iter = fib();while (iter.moveNext()) { print(iter.current);}
无限菲薄纳契数列
JavaScript 1.7*
* Firefox 2.0+ only: https://developer.mozilla.org/en/JavaScript/Guide/Iterators_and_Generators
function fibonacci() { yield 0; yield 1;
var a = 0, current = 1; while (true) { var b = a; a = current; current = a + b;
yield current; }}
for (var i in fibonacci()) { print(i);}
例2:异步编程支持
冒泡排序var compare = function (x, y) { return x - y; }
var swap = function (a, i, j) { var t = a[i]; a[i] = a[j]; a[j] = t;}
var bubbleSort = function (array) { for (var x = 0; x < array.length; x++) { for (var y = 0; y < array.length - x; y++) { if (compare(array[y], array[y + 1]) > 0) { swap(array, y, y + 1); } } }}
冒泡排序动画var compare = function (x, y, callback) { setTimeout(10, function () { callback(x - y); });}
var swap = function (a, i, j, callback) { var t = a[i]; a[i] = a[j]; a[j] = t; repaint(a); setTimeout(20, callback);}
var outerLoop = function (array, x, callback) { if (x < array) { innerLoop(array, x, 0, function () { outerLoop(array, x + 1, callback); }); } else { callback(); }}
var innerLoop = function (array, x, y, callback) { if (y < array.length - x) { compare(array[y], array[y + 1], function (r) { if (r > 0) { swap(array, y, y + 1, function () { innerLoop(array, x, y + 1, callback); }); } else { innerLoop(array, x, y + 1, callback); } }); } else { callback(); }}
outerLoop(array, 0, function () { console.log("done!");});
冒泡排序动画var compare = function (x, y, callback) { setTimeout(10, function () { callback(x - y); });}
var swap = function (a, i, j, callback) { var t = a[i]; a[i] = a[j]; a[j] = t; repaint(a); setTimeout(20, callback);}
var outerLoop = function (array, x, callback) { if (x < array) { innerLoop(array, x, 0, function () { outerLoop(array, x + 1, callback); }); } else { callback(); }}
var innerLoop = function (array, x, y, callback) { if (y < array.length - x) { compare(array[y], array[y + 1], function (r) { if (r > 0) { swap(array, y, y + 1, function () { innerLoop(array, x, y + 1, callback); }); } else { innerLoop(array, x, y + 1, callback); } }); } else { callback(); }}
outerLoop(array, 0, function () { console.log("done!");});
这TMD是什么啊!
如何扩展语言功能?*
• 视JavaScript代码为DSL
• 使用函数的toString方法获得代码
• 使用eval动态执行代码
* 只需JavaScript(ECMAScript 3)标准的支持
函数的toString方法
运行时代码改写var f = function () { ...}
var f = eval(compile(function () { ...}));
运行时编译代码var compile = function (f) {
// 获得函数代码 var code = f.toString();
// 解析代码至语法树 var ast = parse(code);
// 生成新代码 return generateCode(ast);}
动态编译
优点• 载体即为普通JS代码,行为一致• 无需额外编译步骤• 可以使用原有
JavaScript编辑环境
缺点• eval和编译器让人“害怕”
• 必须是合法的JavaScript
平易近人的eval和编译器• eval仅用于保证编程体验(例如:无需额外编译步骤)
• eval和compile仅在启动时执行一次
• 可以在发布前生成编译后的代码• 去除eval和compile的开销• 解除与编译器的依赖• 能够在ECMAScript 5的Strict Mode下执行
Jscex
Jscex是什么?
• JavaScript Computation EXpression
• JavaScript语言扩展,用于某些常见场景下的编程
• F#计算表达式特性的JavaScript移植• 受“计算表达式”特性启发• 为JavaScript设计,基于JavaScript实现
Jscex不是什么?
• 另一种语言• Jscex是百分百的JavaScript
• 框架• Jscex是类库,能够与几乎任何类库/框架一起使用
• JavaScript引擎/运行时• 在任何ECMAScript 3引擎上执行
浅尝辄止
异步编程十分困难
• 破坏代码局部性• 程序员习惯线性地表达算法• 异步代码将逻辑拆分地支离破碎
• 难以• 异步操作之间的协作及组合• 处理异常及取消
冒泡排序动画var compareAsync = eval(Jscex.compile("async", function (x, y) { $await(Jscex.Async.sleep(10)); // each "compare" takes 10 ms. return x - y;}));
var swapAsync = eval(Jscex.compile("async", function (a, i, j) { var t = a[i]; a[i] = a[j]; a[j] = t; // swap repaint(a); // repaint after each swap $await(Jscex.Async.sleep(20)); // each "swap" takes 20 ms.}));
var bubbleSortAsync = eval(Jscex.compile("async", function (array) { for (var x = 0; x < array.length; x++) { for (var y = 0; y < array.length - x; y++) { var r = $await(compareAsync(array[y], array[y + 1])); if (r > 0) $await(swapAsync(array, y, y + 1)); } }}));
http://files.zhaojie.me/jscex/samples/async/sorting-animations.html?bubble
冒泡排序动画var compareAsync = eval(Jscex.compile("async", function (x, y) { $await(Jscex.Async.sleep(10)); // each "compare" takes 10 ms. return x - y;}));
var swapAsync = eval(Jscex.compile("async", function (a, i, j) { var t = a[i]; a[i] = a[j]; a[j] = t; // swap repaint(a); // repaint after each swap $await(Jscex.Async.sleep(20)); // each "swap" takes 20 ms.}));
var bubbleSortAsync = eval(Jscex.compile("async", function (array) { for (var x = 0; x < array.length; x++) { for (var y = 0; y < array.length - x; y++) { var r = $await(compareAsync(array[y], array[y + 1])); if (r > 0) $await(swapAsync(array, y, y + 1)); } }}));
http://files.zhaojie.me/jscex/samples/async/sorting-animations.html?bubble
Jscex异步函数
// 使用异步构造器执行编译后的代码var somethingAsync = eval(Jscex.compile("async", function (...) { // 实现 }));
function () { var res = $await(<async work>);}
响应
function () { var res = $await(<async work>);}
响应
HTTP请求UI事件时钟回调查询响应
Web Service响应代理消息
初始代码
var f = eval(Jscex.compile("async", function () { var img = $await(readAsync("http://...")); console.log("loaded!"); $await(writeAsync("./files/...")); console.log("saved!");}));
var f = eval('(function () { var _b_ = Jscex.builders["async"]; return _b_.Start(this, _b_.Delay(function () { _b_.Bind(readAsync(...), function (img) { console.log("loaded!"); return _b_.Bind(writeAsync(...), function () { console.log("saved!"); return _b_.Normal(); }); }); }) );})');
等价于…
var f = (function () { var _b_ = Jscex.builders["async"]; return _b_.Start(this, _b_.Delay(function () { _b_.Bind(readAsync(...), function (img) { console.log("loaded!"); return _b_.Bind(writeAsync(...), function () { console.log("saved!"); return _b_.Normal(); }); }); }) );});
等价于…
使用Express框架var app = express.createServer();
app.get('/', function (req, res) { /** * 问题:如何进行异步操作?例如: * * 1. 从数据库中获取多个键值。 * 2. 对于每个键,从缓存中获取数据。 * 如果缓存失效,则从数据库里获取数据。 * 3. 将结果列表发送至res中。 * * 注意:所有的“获取”操作都是异步的。 **/});
app.listen(3000);
基于Jscex编程app.getAsync('/', eval(Jscex.compile("async", function (req, res) { var keys = $await(db.getKeysAsync(...)); var results = []; for (var i = 0; i < keys.length; i++) { var r = $await(cache.getAsync(keys[i])); if (!r) { r = $await(db.getItemAsync(keys[i])); }
results.push(r); } res.send(generateList(results));})));
Jscex vs. 其他异步方案• 许多项目都在设法简化异步编程难度• 异步类库• Virtual Panel: How to Survive Asynchronous
Programming in JavaScript
• 异步语言• StratifiedJS
• Narrative JavaScript
• Streamline(可能是与Jscex最相似的项目)
Jscex vs. 异步类库• 异步类库• 在类库建立的规则内编写代码• 有限的灵活程度与表达能力• 需要较多学习
• Jscex• 使用JavaScript表达逻辑• 极高的灵活度和表达能力• 只需少量学习
Jscex vs. 异步语言• 异步语言• 学习新语法• 学习新语义• 改变传统编程体验
• Jscex• 完全使用JavaScript语法• 完全保留JavaScript语义• 完全保留JavaScript编程体验
Streamline
• 可能是与Jscex最相似的项目• 保留JavaScript语法、语义• 简单修改即可JIT编译• 无需独立的异步操作语句
• 与Jscex相较最大的区别• 目标代码生成方式• Yield – Resume vs. Asynchronous Callbacks – An
Equivalence
Streamline (input)
var bubbleSortAsync = function (array, _) { for (var x = 0; x < array.length; x++) { for (var y = 0; y < array.length - x; y++) { if (compareAsync(array[y], array[y + 1], _) > 0) swapAsync(array, y, y + 1, _); } }}
Streamline (compiled)
http://sage.github.com/streamlinejs/examples/streamlineMe.html
var bubbleSortAsync = function __1(array, _) { if (!_) { return __future(__1, arguments, 1); }; var __then = _; var x = 0; var __4 = false; return function(__break) { var __loop = __nt(_, function() { var __then = __loop; if (__4) { x++; } else { __4 = true; } ; if ((x < array.length)) { var y = 0; var __3 = false; return function(__break) { var __loop = __nt(_, function() { var __then = __loop; if (__3) { y++; } else { __3 = true; } ;
if ((y < (array.length - x))) { return compareAsync(array[y], array[(y + 1)], __cb(_, function(__0, r) { if ((r > 0)) { return swapAsync(array, y, (y + 1), __cb(_, __then)); } ; return __then(); })); } else { return __break(); } ; }); return __loop(); }(__then); } else { return __break(); } ; }); return __loop(); }(__then);};
Jecex (input)
var bubbleSortAsync = eval(Jscex.compile("async", function (array) { for (var x = 0; x < array.length; x++) { for (var y = 0; y < array.length - x; y++) { var r = $await(compareAsync(array[y], array[y + 1])); if (r > 0) $await(swapAsync(array, y, y + 1)); } }}));
Jecex (compiled)var bubbleSortAsync = (function (array) { var _b_ = Jscex.builders["async"]; return _b_.Start(this, _b_.Delay(function () { var x = 0; return _b_.Loop( function () { return x < array.length; }, function () { x++; }, _b_.Delay(function () { var y = 0; return _b_.Loop( function () { return y < (array.length - x); }, function () { y++; } _b_.Delay(function () { return _b_.Bind(compareAsync(array[y], array[y + 1]), function (r) { if (r > 0) { return _b_.Bind(swapAsync(array, y, y + 1), function () { return _b_.Normal(); }); } else { return _b_.Normal(); } }); }), false ); }), false ); }) );})
Jecex (compiled)var bubbleSortAsync = (function (array) { var _b_ = Jscex.builders["async"]; return _b_.Start(this, _b_.Delay(function () { var x = 0; return _b_.Loop( function () { return x < array.length; }, function () { x++; }, _b_.Delay(function () { var y = 0; return _b_.Loop( function () { return y < (array.length - x); }, function () { y++; } _b_.Delay(function () { return _b_.Bind(compareAsync(array[y], array[y + 1]), function (r) { if (r > 0) { return _b_.Bind(swapAsync(array, y, y + 1), function () { return _b_.Normal(); }); } else { return _b_.Normal(); } }); }), false ); }), false ); }) );})
outer loop
inner loop
$await(compareAsync(...))$await(swapAsync(...))
Jscex代码生成模式• 代码生成模式简单而直接• 能够轻松映射至原有代码• 易于调试:使用“debugger”语句暂停代码,或在调试器中设置断点
• 为JavaScript设计的标准方法• Start, Delay, Combine
• Loop, Try
• Normal, Return, Break, Continue, Throw
• Bind
调试浏览器脚本
调试Node.js脚本
... the principle we go by is, don't expect to see a particular concurrency model put into C# because there're many different concurrency model ... it's more about finding things are common to all kinds of concurrency ...
- Anders Hejlsberg
语言与类库• 编译器(语言扩展)• JIT:在运行时生成代码(用于开发环境)• AOT:在运行之前生成代码(用于生成环境)
• 构造器(类库)• 异步构造器:简化异步编程(如F#,Scala,Python
Twisted,C# vNext等等)• 序列构造器:延迟加载的迭代器(如Python,C#,
JavaScript 1.7等等)• 更多…
Jscex构造器
• 编译时提供构造器名称• 构造器向编译器提供“绑定”操作名称
• 编译器根据“绑定”操作名称转化代码
• 由定义在构造器上的标准方法执行代码
不仅仅是异步编程// 无限的菲波纳契数列var fib = eval(Jscex.compile("seq", function () { $yield(0); $yield(1);
var a = 0, current = 1; while (true) { var b = a; a = current; current = a + b;
$yield(current); }}));
https://github.com/JeffreyZhao/jscex/blob/master/samples/seq/fib.html
函数式扩展
https://github.com/JeffreyZhao/jscex/blob/master/samples/seq/fib.html
fib() .filter(function (n) { return n % 2 == 0; }) .skip(2) .take(5) .zip(infinite(1)) .map(function (a) { return a[1] + ": " + a[0]; }) .each(print);
轻松构建扩展var filter = eval(Jscex.compile("seq", function (iter, predicate) { while (iter.moveNext()) { if (predicate(iter.current)) { $yield(iter.current); } }}));
var map = eval(Jscex.compile("seq", function (iter, mapper) { while (iter.moveNext()) { $yield(mapper(iter.current)); }}));
var zip = eval(Jscex.compile("seq", function (iter1, iter2) { while (iter1.moveNext() && iter2.moveNext()) { $yield([iter1.current, iter2.current]); }}));
https://github.com/JeffreyZhao/jscex/blob/master/src/jscex.seq.powerpack.js
还有Maybe Monadvar maybeFunc = function () { var maybeA = getA(); if (maybeA != Maybe.None) { var maybeB = getB(); if (maybeB != Maybe.None) { return maybeA.value + maybeB.value; } else { return Maybe.None; } } else { return Maybe.None; }}
// 简化后的版本var maybeFunc = eval(Jscex.compile("maybe", function () { var a = $try(getA()); var b = $try(getB()); return a + b;}));
总结
• 视JavaScirpt为DSL• 视引擎为编译+运行环境• JavaScript提供了完备的“无缝”改写代码的机制
• 现在开始尝试Jscex• 基于BSD协议发布• https://github.com/JeffreyZhao/jscex(英文)• http://www.sndacode.com/projects/jscex(中文)
Q & A
谢谢