2008年11月29日星期六

Understanding Scope

Scope是Programming Languages中的重要概念。我的第一门Programming Language是C++,记得当时买的参考书专门用了一章的篇幅来讲解scope,罗嗦了一大堆,当时搞清楚了,事后很快就忘记了。后来学习Java,PHP和Ruby等语言,都有scope的概念,不过都大同小异。掌握了scope的内涵,任何语言的scope都不难学习。

What is Scope?
Wikipedia上对scope的定义是[1]:

In computer programming, scope is an enclosing context where values and expressions are associated.
首先,scope是一个"enclosing context",它是一个上下文环境,并且是封闭的。其次,在这个环境中,"values and expressions are associated",也就是说在这个环境里,expression被绑定到特定的值,但是出了这个环境,该expression就不再绑定到这个值了。

有一个笑话,话说南京人把三轮电动摩的称作“马自达”,这种车体积小巧,马力不大,开起来噪音很大,而且外壳也不是很牢固。在乡下开开还可以,要是在城里主干道上行驶,不仅危险系数很高,而且还会影响市容。所以在金陵饭店门口挂了个牌子:马自达不得入内。于是外地开着马自达来南京的商人,看到牌子都被吓跑了。

同样是“马自达”这个词(expression),在全国这个scope中指汽车马自达(value),而在南京scope中就变成了三轮摩的(value)。古语有云:“橘生淮南则为橘,生于淮北则为枳”,也是这个意思。由此可见,无论是expression还是其他事物,都应该放在特定的scope和环境中进行评价,很有哲学意味吧。

有些语言提供了namespace的机制,比如C++中的using namespace和ruby中module。Namespace也是一个scope,只不过它用了一个标识符来表示这个scope。这样,在谈论expression时,就可以指定在哪个scope中确定其值。比如上面的笑话中,在“马自达”前加上“南京话中的”这个状语,就不会引起误会了。

Why Scope?
在计算机发展的早期,内存大小和CPU的计算能力都很有限,程序通常也很小,数据被存放在一个地方,程序的任何部分都可以访问数据。但是,随着计算机的发展,程序变得越来越庞大,有很多部分组成,也需要很多程序员共同开发而成。这样,把程序不同部分需要的数据混合在一个地方就会造成混乱。比如程序的不同部分中用到相同的变量名,但是它们表示的是不同的数据,这样就很难确定程序的不同部分是否使用了正确的数据。

所以,就需要引入scope的特性,这样就能控制程序的不同部分访问它们各自的数据。通常,scope有两个作用。(1) define the visibility[1]:定义变量在特定的scope中才可见。这样,默认地,程序的某个部分只能访问该部分中的数据,而不会访问其他部分的数据。最常见的情况是在程序的不同部分可以定义相同名称的变量,它们指向不同的数据,而不会引起命名的冲突。(2) reach of information hiding[1]:得到隐藏的信息。比如使用namespace来访问其他scope中的数据。

Lexical Scoping
根据scope的定义,scope是expression和value关联的地方,那么,是根据什么原则把expression和特定的value关联到一起的呢?有两种scope类型:lexical scoping和dynamic scoping。先来说说lexical scoping。

Lexical Scoping(又称Static Scoping)有很多定义[5],但从其名称可知,lexical scoping只与程序语句的组织有关,而与程序运行时无关。变量只在其定义的block中可见,离开此block变量就不存在了。因此,在编译的时候就可确定变量绑定的地址,即可通过分析程序本身来确定变量的绑定,而无须运行程序。Lexical scoping意味着[4]:

  • an identifier at a particular place in a program always refers to the same variable location — where “always” means “every time that the containing expression is executed”, and that
  • the variable location to which it refers can be determined by static examination of the source code context in which that identifier appears, without having to consider the flow of execution through the program as a whole.
现代的Programming Languages大多使用lexical scoping,比如C,C++,Java,Ruby,PHP,Scheme等。

如下用C写的代码:


int x=0;
int f() { return x;}
int g() { int x=1; return f();}

or in Scheme:

(define x 0)
(define (f) x)
(define (g)
    (let ((x 1))
    (f)))
函数g的返回值为0,因为在定义函数f的时候,x绑定到全局的x,即0,所以无论什么时候运行f,结果都是0,所以g的结果也为0。

对于不支持High Order Procedure的语言(如C),实现lexical scoping只需使用一个symbol table记录变量的scope和memory address,从中查询变量即可,这是很高效的,因为每个变量的位置在编译时就确定了。而对于支持High Order Procedure的语言(如Scheme),则需要为每个函数保存其定义时依赖的环境(函数及其定义的环境组成了一个Closure)[6]。

使用lexical scoping有助于代码的模块化,也有助于程序员根据绑定来推出变量的值,减少错误的发生。这也是现代Programming Languages大多使用lexical scoping的原因。

Dynamic Scoping
Dynamic Scoping无法在编译时确定变量的绑定,需要在程序的运行过程中才能确定。在运行过程中,每个变量都有一个对应的stack来保存绑定。例如,每次变量x被定义的时候,都会向x对应的stack X中push最新的绑定,当x离开当前scope后,就pop stack X。访问x的值时,每次获得的都是栈顶的绑定。

早期的Lisp语言都是使用dynamic scoping的,后来都渐渐加入了lexical scoping特性。现在Emacs Lisp仍然使用dynamic scoping。而Perl和Common Lisp则允许变量定义时指定其scope类型。

这时,在上面的例子中,函数的返回值为1。因为在g的函数体内定义了x=1,这时就向x的Stack中push了x=1的绑定,这时再调用函数f,f返回的x则是从栈顶取出来的绑定,即x=1。在这个例子中,函数f的返回值不再是确定的,它依赖于被调用时x的值。

Dynamic scoping比较容易实现。在寻找某个变量的值时,可以遍历activation record,直到找到为止,这种方法叫作deep binding。还有一种效率更高的方法,如上描述,为每个变量维护一个绑定的stack,每次只需对栈顶元素进行操作即可,这种方法叫作shallow binding。如下图所示:

Dynamic scoping可以给程序带来很大的灵活性,在实现函数的时候不需要去推理变量的绑定,而只需专注于系统的当前状态。利用这种好处需要有良好的文档支持。但是它的缺点也很明显,首先它不利于模块化,设想一下如果使用dynamic scoping,那么现在那么多的java framework都可能无法正确运行。其次,由于无法知晓运行时的环境,会给程序带来意想不到的危险。因此,现代programming language几乎都不使用dynamic scoping。

Scope的一个重要作用是解决命名冲突。设想一个极端的情况,程序中所有变量都不重名,那么无论使用lexical scoping还是dynamic scoping,运行结果都是一样的。由此可见,两者的差别在于对重名变量的绑定方式的不同。从本质上讲,对于每个标识符,两者都需要维护一个stack来决定当前使用的绑定,而lexical scoping是在编译的时候维护这个stack,而dynamic scoping则是在运行时维护。

Conclusion
Scope是决定expression与value关联的上下文环境。使用Scope可以解决命名冲突,定义变量可见性,以及获取隐藏的信息。Lexical scoping和dynamic scoping是scope的两种类型。

References
[1] Scope (programming) on Wikipedia.org
[2] Scope in Programming Languages
[3] Introduction to Programming/Scope
[4] Lexical Scope on GNU.org
[5] Lexical scope definition
[6] Structure and Interpretation of Computer Programs

2008年11月26日星期三

惊魂一刻

Kubuntu 8.10发布快一个月了,因为担心升级后会出现问题影响日常使用,所以一直使用着经典的KDE 3.8。上周末终于禁不住诱惑,狠下心升级到了8.10。升级过程非常顺利,没有出现任何问题。虽然KDE 4谈不上“惊艳”,但我还是挺喜欢的,界面设计很精致,很professinal,特别喜欢新的Konsole,默认的配色很漂亮。唯一有问题的是播放器,无论是mplayer,smplayer,还是kaffeine,打开视频文件后,都会导致黑屏,就只能重启系统了。kaffeine有50%的可能出错,而mplayer和smplayer几乎每次都会出错。由于kaffeine还没有KDE 4的版本,现在我使用的还是KDE 3的版本,所以也可以理解。

使用的这几天,每天播放视频都会黑屏,所以今天黑屏后,依然如往常强行关机、重启。这次重启时在check /home分区时出错了,提示需要手动运行fsck。以前也遇到类似的问题,就顺手运行:

$ fsck

啊!忘记指定分区了,这样就去check主分区了,直接导致了主分区损坏,重启后grub报error 17错误,无法进入系统了。

不急不急!我有Live CD。运行Live CD后就开始google了。看了“Grub Error 17 问题之简单解决“后心想可以解决了。可是运行find命令的时候,又报出grub error 15的错误。搜出来的解决方案也很多,进入/boot查看内核文件之类的,这时我发现了问题的所在。在使用如下命令mount主分区时,提示这是不正确的文件系统类型,而同样的做法mount /home分区却是成功的。

$ sudo mount -t ext3 /dev/sda1 /media

由此可见,一定是刚才的fsck导致/dev/sda1上的文件系统损坏了。又google了一下,几个帖子都说他们最后的解决方法都是重装系统。这时心里一惊,真的要重装吗?订的Kubuntu 8.10的盘还没到,手头又只有7.10的盘。刹那间,一个念头闪过,何不用fsck再修复一下/dev/sda1呢?

$ sudo fsck -y /dev/sda1

这下系统能启动了,再用fsck修复一下/home分区就OK了。

$ fsck /dev/sda5

一个小时后,又重新进入系统了。一个误操作导致了虚惊一场,不过还是希望能快点解决播放视频黑屏的问题,这样我也不用整天强行关机了。

2008年11月12日星期三

立冬苦吟

残阳西下,秃树黄叶风尘沙。歌舞声罢,月似狼牙,寒光照枯桠。去时何足夸,来日泪无涯。老骥瘦马,看尽长安花。

2008年11月1日星期六

下车

每周四晚上都会去本部上课,九点下课搭同一班车回去。跟往常一样,今天车上的人也挺多的。根据经验,往后门附近走,很快就会有位子。我一个劲挤到了车后面。果然,没过几站,就在倒数第二排找到了座位。刚坐下,从斜对面的座位上跑过来一个小学生,手里抱着装满东西的购物袋,一边在我旁边的座位坐下,一边朝我后面的座位喊了声“妈妈”。原来是母子俩一起出来的。这一幕勾起了我小时候跟妈妈进城的回忆。

汽车过了一站,又有一些乘客下车了,车里已经不挤了。见最后一排有个空位,小学生就爬了上去。他妈妈连忙阻止他,叫他别上去,那边前面没有遮挡,不安全。这时坐在旁边的一个男人站了起来,让小学生坐在里面的一个位子。小学生边坐下,边说:“没关系,这不还有我爸在嘛。”原来那个男人是他爸。他们一家三口就这样坐在汽车的最后一排,儿子在父母中间。

车里的电视正在播放一个关于离婚调解的节目,我看得出神,没有再关注他们。只是小学生的一句:“真希望现在能堵一会车。”让我的注意力从电视转回车内。

车又进站了,那个小学生抱着购物袋,他爸爸提着书包,在车门前等候下车。可是他妈妈没有下车,只见小学生朝我后面挥手告别。直到下车了,他还在路边依依不舍的挥手。这让我感到十分疑惑,为什么他们没有一起下车呢。

车快要到达终点站了,车里显得空荡荡的。我很想回头看看,可是我仍然望着窗外。只是偶尔能听到后面传来的阵阵抽泣声。

2008年10月31日星期五

我是灾星

闲来无事,回想了两三年来发生的一些事情,发现它们都有一个共同点,就是原本好端端的事情,只要我一涉足,就会因为种种原因而不能顺利完成。看来,我的确是个“灾星”。

大三上学期,突然萌生了兼职的念头。在网上投了几份简历,有一家公司很快就联系了我。这是一家美国公司,招兼职的是一个华人留学生,他说希望能把这样的机会留给中国的学生。还打了几次越洋电话给我,说对我的情况挺满意,已经提交给他的leader了,问题不大,不过希望我能去学习一下PHP。于是花了一个星期学会了PHP。之后,就被告知由于投资商的资金出了问题,这个项目被无限期搁置了。至于是如其说言,项目真的无法进行,还是他的leader不同意录用我而找的借口,就不得而知了。

大三下学期,一个同学的叔叔在政府工作,负责一个项目,做一个网站,收集全市几百家企业的信息,供政府内部使用。由同学介绍找到了我,通了几次电话,确定要把项目交给我做了,唯一的要求就是要找一个老师过去跟他的领导谈一下项目开展及酬劳问题,因为他的领导是一个上了年纪的老干部,我们这种“小孩”去跟他谈太嫩了,不合适,以后开发的事情就可以由我自己完全负责。去找了老师,不过那段时间比较忙,要到月底才行。对方说时间方面没关系,只要能过去一次就可以了,我可以提前过去聊一下需求之类的,这样也算是确定要做这个项目了。于是约好了同学,买好了火车票,准备周末就过去。这样的行程准备了两次,但都没去成,第一次是因为市里出了交通事故,第二次是因为省里领导要过去检查。一托就到了月底,最终老师也没去谈,这里面的种种也不提了。等我暑假回去的时候,对方说之前是领导刚上任,俗话说“新官上任三把火”,所以想弄个政绩工程,现在过了段时间,位子坐稳了,就不想做了。有机会的话以后再跟我合作。

大四开学前一个月,回到学校,网上有个创业团队在招人,对用到的技术挺感兴趣的,就加入了。团队有五、六个人,都是南大软院或计算机系的。跟带头的人见了面,感觉不错,还签了保密协议等文件。说是创业,其实也就是几个学生做个Web项目,然后上线测试。我负责数据库的设计和Lucene搜索的实现。开始的时候每周都有例会,大家热情都很高。计划10月份就要上线。我花了不到两周的实现把手头能做的工作基本都做了,接下来就是等另一个伙伴工作完成,我再继续。可是一周,两周,三周都没有等到,连例会都没出现。而每次例会都在反反复复的讨论一定要用什么技术,这个功能是现在实现还是以后实现,很久以后我们应该要有什么功能……进度几乎停止。随着大家热情的消退,并且都忙于个人事务,项目实际上已经停止。于是上线时间从10月推到11月,又从11月推到12月。后来,带头的人终于无奈的宣布项目失败。至今,那段Lucene代码仍躺在我的workspace中。过了段时间再跟他聊的时候,他告诉我他对Rails和REST十分着迷,决定彻底放弃Java。

从大二开始学习J2ME,之后做了几个相关的项目,几乎每天都会上j2medev学习交流,因此认识了站长mingjava。大四刚开学的时候,得知我想保研去北京,他就邀请我去他创业的公司实习。我很感兴趣,后来去北京保研面试的时候还特地去他的公司参观了一下。9月底保研结果就出来了,当时几乎决定国庆之后就要去北京实习了,mingjava也开始替我关注租房的信息。后来发现保了研之后还有很多手续要办,体检、报名、采集信息,一直要到11月底才能全部完成。这与他希望的时间出入太大,所以只能放弃了这次机会。心想以后到了北京,还是有机会的。到了次年4月份再跟他聊的时候,得知由于投资方觉得没有前途,项目已经终止了。

大四下学期临近毕业了,曾经合作过的黄san跟我说他朋友有个项目,后台有些工作要做,就是设计下数据库,用Spring写点service,挺简单的,想让我花个十天半个月做一下,“顺便补充一下小金库”。过了三天,他告诉我项目快定了,很快就可以做了。之后就再也没有下文了,我想可能又是因为项目没能开展吧。

我一直认为读研的最大收获是遇到了我的导师,一年跟着他学到了很多。今年4月份的时候,他在邮件中告诉我们他一直在筹建的实验室一个月之内就可以完成了,感兴趣的学生可以进入实验室跟他合作。我当时一心打算在他的实验室做到毕业,从没计划去企业实习。可是到了5月,他告诉我们他换了投资方,所以要过一段时间才可以;6月份的时候,他告诉我们他正在做demo,可能要过三四个月才行;9月份的时候,他告诉我们:你们还是先找个实习吧,去公司体验体验,等我把实验室建好了,你们感兴趣的话还可以回来,如果我们没能合作也没关系,只要你们学到了做研究的方法就够了。于是,我只能开始找实习了。

刚开始找实习的时候并没打算全职工作,就想找个兼职,解决下生计问题,还可以有很多时间自己看书学习。看到一个院里师兄发的兼职信息,跟他聊了一下,他觉得我挺合适的,不过因为他之前的两个项目已经完成了,都处在维护期,所以还没什么工作可做。有一个项目正在谈,下周就会有定论。第一次跟合作者约好了时间,结果对方因为有事推迟了,第二次因为之前的事情没有解决,又没谈成。他在邮件里告诉我约好了下周谈,从此就再也没有回音,至今不知道谈没谈成。

虽然兼职没有找到,但是很幸运,我被甲骨文录取为实习生了。据说是某个老大提出要做一个项目,特地招实习生去做这个项目的,周期为8个月。面试的时候听说这个项目肯定要做,而且挺急的,所以最好能尽快on board。可是现在上班还不到一个月,老大们就“拍脑袋”说不做了,要知道在这段时间里,技术调查工作已经基本完成,而带我的工程师已经把项目的架构都写好了。所以现在还不明确我接下来要做什么事情,下周才能确定我的工作内容。想到是因为当初要做项目才招我为实习生的,现在却要花7个月的时间做别的工作,真的挺搞笑的。

To be continued...

成功,我还没上路唉。