庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

闭包使用的一个陷阱

Posted on 2010-07-09 23:52 dennis 阅读(1705) 评论(4)  编辑  收藏 所属分类: 动态语言

来源:http://moonbase.rydia.net/mental/blog/programming/the-biggest-mistake-everyone-makes-with-closures.html

看下面的Ruby代码
= []
for x in 1..3
  k.push(
lambda { x })
end

执行
k[0].call

你可能预期返回1,实际的结果却是3。这是为何?这是因为在迭代过程中共用了同一个context,导致k中的三个闭包都引用了同一个变量x。不仅仅Ruby有这个问题,python也一样
= [lambda: x for x in xrange(14)]
k[0]()

Javascript同样如此

var k = [];
for (var x = 1; x < 4; x++) {
  k.push(function () { 
return x; });
}
alert(k[0]())


解决这个问题很简单,就是将闭包包装到一个函数里,建立新的context,那么迭代过程中生成的闭包所处的context不同:
def make_value_func(value)
  
lambda { value }
end
= (1..3).map { |x| make_value_func(x) }

这个时候,k[0].call正确地返回1。

这个问题并非在所有支持闭包的语言里都存在,例如scheme中就没有问题

(define k '())
(do ((x 1 (+ x 1)))
    ((
= x 4'())
  (set! k (cons (lambda () x) k)))
(set! k (reverse k))

((car k)) 
=>1


Erlang也没有问题
K=[ fun()->X end || X <- [1,2,3]].

lists:map(fun(F)
-> F() end,K).

再试试Clojure:
(def k (for [i (range 1 4)] (fn [] i)))
(map #(
%) k)

同样没有问题。这里Erlang和Clojure都采用列表推断。





评论

# re: 闭包使用的一个陷阱  回复  更多评论   

2010-07-10 00:40 by Rain Yang
Ruby 1.9.x 不会再这样了。

# re: 闭包使用的一个陷阱[未登录]  回复  更多评论   

2010-07-15 14:46 by sin
actionscript同样问题

# re: 闭包使用的一个陷阱  回复  更多评论   

2012-12-22 15:57 by Zhiqiang.Zhan
这不是闭包的缺陷,这是因为Ruby,Python还有JavaScript这些支持函数式等编程语言中的block都不能形成闭包作用域,而你拿来对比的Erlang和Clojure的例子很不恰当,它的等价形式相当于Ruby中的
k = []
(1..3).each do |x|
k.push(lambda {x})
end

puts k[0].call

而这个刚好是OK的。
这是因为do...end block形成了闭包,但是for ...end中的语句没有形成新的闭包。

# re: 闭包使用的一个陷阱  回复  更多评论   

2013-11-04 02:45 by 我傻逼我自豪
js例:
var a = [];
for (var x = 4; x >= 0; x--) {
a.push(function (x) {
return x;
});
}
alert(a[4]());

只有注册用户登录后才能发表评论。


网站导航: