C#与Ruby 1.8在作用域与闭包上的比较

在读Programming Ruby,突然想起以前在别人的blog上回过的一帖,顺带再回忆一下在自己blog上也记下来。

先看两个例子:
testClosure.cs:
using System;
using System.Collections.Generic;

sealed class TestClosure {
static void Main(string[] args) {
var actions = new List<Action>();
for (int i = 0; i < 3; ++i) {
int j = i;
actions.Add(() => Console.WriteLine(j));
}
foreach (var a in actions) {
a();
}

// Output:
// 0
// 1
// 2



actions = new List<Action>();
for (int i = 0; i < 3; ++i) {
actions.Add(() => Console.WriteLine(i));
}
foreach (var a in actions) {
a();
}

// Output:
// 3
// 3
// 3
}
}


testClosure.rb:
procs = []
(1..3).each do |i|
procs << lambda { puts i }
end
procs.each { |p| p.call }

# Output:
# 1
# 2
# 3

###############################################

procs = []
for j in (1..3) do
procs << lambda { puts j }
end
procs.each { |p| p.call }

# Output:
# 3
# 3
# 3


在上面的两段代码里,可以看到C#与Ruby有很相似的行为——这是因为C#中的delegate和Ruby中的block都是闭包(closure),所以它们可以“记忆”不在当前作用域内的变量(或者说非局部变量)。但闭包所记忆的并不一定是[b]值[/b],而可能是[b]引用[/b];在C#与Ruby中的闭包都是记住引用的,而像纯函数式语言则是记住值(其实没区别,纯函数式语言里的“变量”的值改变不了)。
既然C#和Ruby的闭包都是记引用,那为什么上面每组测试的前一个与后一个看起来行为不一样?

其实每组测试的前后两部分正好说明它们的本质是一样的。[b]闭包[/b]的概念与[b]静态作用域[/b]是紧密相关的。在C#的测试中,循环的控制变量i作用域覆盖整个循环过程;或者说整个循环过程使用到的i都是同一个i;前后两部分测试的结果不一样,是因为前一个测试里闭包所捕获的是只在一次循环里有效的j,每一轮循环中的j都不是“同一个”j;后一个测试的闭包捕获的是整个循环过程都有效的i,每一轮循环中的i都是同一个i,所以后续循环轮次中对i的改变都会影响到闭包所捕获的值。

Ruby方面同理。在Ruby 1.8.x中,与each迭代器关联的block里的变量是局部变量,每次each中使用yield来调用block时,block都会创建新的局部变量。而for与each的区别就是在循环变量的作用域上:for被展开后,先定义了一个局部变量,然后再调用each迭代器。
印象中上个月还是什么时候在经过别人的blog时见到过对Ruby的for的讨论,正好今天读到Programming Ruby时也看到了这介绍,记下。

不过在Ruby 1.9里,block的作用域有所改变。像这样:
x = 15
(1..5).each do |x|
puts x
end
puts x # -> 15

(这消息来自[url=http://learnruby.com/ruby-1.9.html]Matz on Ruby 1.9[/url])
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值