for loop is EVIL !!
12 December 2011
for (int i = 0; i < LENGTH; ++i) { print(i); } |
无论是C++, JAVA还是javascript, 这种for循环都是非常常见的代码; 但是在所有的函数式语言中: Scala, Scheme, Erlang, Haskell为什么语法中都没有这样的for循环?
考察下面这段代码:
var a = ['a.html', 'b.html', 'c.html']; for (var i = 0; i < a.length; ++i) { console.log(a[i] + ": " + a[i].length); } |
运行起来还算正常
现在修改一下需求, 返回这几个页面的字符数:
var a = ['a.html', 'b.html', 'c.html']; for (var i = 0; i < a.length; ++i) { $.ajax(a[i], { success: function (text) { console.log(a[i] + ": " + text.length); } }); } |
看起来这段代码与上面那段没什么太大区别, 应该不会出问题, 但是实际应用中, 立刻就看到问题了, 这段代码输出:
|
原因很简单, 虽然闭包success捕获了变量i, 但是i在后面发生了变化, 调用success闭包的时候, i变成了3, a[i]返回undefined.
解决这个问题的方法也很简单: 不要使用变量
var a = ['a.html', 'b.html', 'c.html']; $.each(a, function (k, v) { $.ajax(v, { success: function (text) { console.log(v + ": " + text.length); } }); }); |
由于传统的for循环必然引入变量, 一般的函数式语言的语法中都不包含传统的for循环. == Scala == for循环是一个语法糖:
for (i <- a) { foo i } |
相当于:
a each { i => foo i } |
每次都会生成一个新的变量i, 以及包含这个i的新闭包 为了强调i不是变量, Scala标准废弃了下面这种语法:
for (var i <- a) { foo i } |
Scheme
Scheme的do循环其实是一个宏:
(do ((i 0 (+ i 1))) ((= i 5) i) (foo i)) |
展开后就是一个递归调用:
(letrec ((loop (lambda (i) (if (= i 5) i (begin (foo i) (loop (+ i 1))))))) (loop 0)) |
对列表操作的时候最好使用srfi-1的map函数:
(map (lambda (i) (foo i)) a) |
Haskell
Haskell提供的循环:
[ foo i | i <- a ] |
或者使用list monad:
do i <- a return (foo i) |
可以看出, 所有的函数式语言提供的for循环都没有使用变量, 变量在函数式语言中天生水土不服. javascript本身就是个畸形, 标榜自己是函数式语言, 却处处学习C/java的语法, 在使用javascript的时候, 最好尽量避免使用传统的for循环