2009年2月7日星期六

Personal Wiki

我这人有两个毛病:记性差和喜新厌旧。明明当时记得很清楚的事情,过了3、4天就忘得差不多了。但是又对新事物特别感兴趣,比如技术,看到新的技术,就会放下手头的东西去研究新的,导致我的很多知识都是浅尝辄止,而且时间一长,连基本的都忘记了,等下次需要用的时候,又得从头学起。这时又只能到处去找文档和教程。所以决定使用Wiki作为个人知识库,在学习时把有用的资料记录下来,以便日后需要时能够很容易地找回来。

这是一个仅供自己使用的Wiki,所以安装在本机上即可,至于多用户、版本控制等与协作相关的功能都不是必需的,只要满足如下条件即可:
  1. 尽可能的lightweight,使用文件作为存储,不需要数据库和web server
  2. 中文支持当然是必需的,并且必须支持Linux平台
  3. Open source,最好是使用我熟悉的语言写的,以便能自己进行修改
  4. 能方便地进行定制,修改布局,风格等
  5. 能方便地进行备份和迁移
首先推荐一个网站:WikiMatrix,通过输入一些要求,它就会根据这些要求推荐一些Wiki系统,并给出对它们进行比较的结果。经过一些筛选,最后确定3个候选的Wiki:
  • Wiki on a Stick:它只有一个html页面,使用JavaScript+CSS+HTML完成。所有的内容都保存在一个文件中,由JavaScript来切换显示的内容。这个创意很Cool,小巧且容易移动,可以将其存放在邮箱或U盘中。它的界面风格也很简洁,正是我喜欢的类型。但是它现在仍处于Beta阶段,如其页面上的提示:这是Beta版本,不建议用来保存关键数据。再看看最近一次更新也已经是一年半前的了,track中也没什么activity,项目似乎处于停滞阶段。而且,我也没看到Wiki on a Stick有成熟的user community,所以我最后没有选择它。
  • TiddlyWiki:TiddlyWiki与Wiki on a Stick如出一辙,在切换内容的时候加入了一些animation的效果,使界面看起来更“Web 2.0”一点。Journal是Wiki on a Stick没有的功能,用来记录某一天做的事情。TiddlyWiki现在的版本是2.4.3,应该算比较稳定了。最重要的是,它有很优秀的文档和user community,还有很多基于TiddlyWiki的变种,这使我最终选择了它。但是有两点我不是很喜欢,首先是它默认的风格,其次是在切换内容的方式,它并不是先隐藏之前的内容再显示新的内容,而是将新的内容放在页面顶端,经过多次切换后,页面就会上下跳动,让人很不舒服。这两点都是可以修改的,以后要研究一下。
  • doxWiki:这也是一个很小巧的Wiki,用Perl写的,同时自带了一个用Perl写的server,使用之前需要运行该server。它有一些不错的功能,比如导出html文件。
更多关于以上三个Wiki的比较,请看这篇文章:Personal wikis: Three small, simple alternatives。

除了搭建在本机的Wiki,还存在着一些提供Wiki服务的网站,可以在上面创建自己的Wiki页面。对我来说,这样的好处是可以在家里和公司访问Wiki。但是也有一些缺点,比如访问速度不快,受制于服务提供商的一些政策,没有网络的时候无法访问,所以我还是选择自己安装Wiki。在筛选的过程中找到了几个免费的不错的Wiki hosting网站:
  • Wiki Spot:二级域名,简洁现代的界面,多种主题……都是我的最爱,只是Wiki Spot的目的旨在建立Wiki community,它要求每个Wiki的主题必须能够形成一定的用户群,这与我的目的不符,所以只得作罢。
  • @Wiki:除去基本功能,提供了很多主题,页面操作也很简单明了,不过不支持二级域名。
  • Intodit:二级域名,主题比较少,不同主题布局都是一样的,只是颜色不同而已。不是很喜欢它的button风格,在firefox中很难看。可定制的部分不是很多,不喜欢自带的那几个tab页。
先花点时间研究一下TiddlyWiki,如果你有更好的选择,欢迎推荐给我。

2009年1月5日星期一

兰亭风花

从《东风破》的离愁,到《菊花台》的悲凄,到《青花瓷》的温婉,再到如今《兰亭序》的无奈,方文山将古典诗词的神韵融入了歌词之中,首首歌词都蕴含着风雅之音,如《东风破》易让人想起“相见时难别亦难,东风无力百花残”的名句。于我而言,尤推《兰亭序》为挚爱,这首词将古典诗词的文字和意境之美发挥到了极致。信手搜索“兰亭序”,竟在百度百科的解释中读到对这首歌词的赏析,诸多观点与我不谋而合,故借其一二,完成此文。

兰亭临帖 行书如行云流水
月下门推 心细如泥脚步碎

夜深人静,挑灯临摹,笔尖行于宣纸之上,思绪如云,心境如水。月光皎洁,木门轻推,但闻细碎的脚步声渐近。唐代苦吟诗人贾岛有诗:“鸟宿池边树,僧敲月下门。”,极具禅意。而正是这两句诗引出了他与韩愈的千古佳话,“推敲”一词由此而来。

忙不迭 千年碑易拓却难拓你的美
真迹绝 真心能给谁

流传千年的碑文容易拓,而爱人的美貌却无人能够模仿比拟。《兰亭集序》的真迹已经不复存在,我的一片真心又能与谁呢?相传唐太宗李世民极其喜爱王羲之的书法,经过一番周折终于得到《兰亭集序》的真迹,最后此真迹也作为唐太宗的殉葬品而永绝于世。如今所见的《兰亭集序》都是后世的摹本或拓本,最有名的要数“神龙本”和“定武本”,其中“定武本”出自唐代大书法家欧阳询之手。摹写真迹已为难事,而描摹爱人的美却要难上千倍。

牧笛横吹 黄酒小菜又几碟
夕阳余辉 如你的羞怯似醉

杜牧诗曰:“清明时节雨纷纷,路上行人欲断魂。借问酒家何处有,牧童遥指杏花村。”同有牧童和酒家,但两处的意境却不尽相同。《清明》的前两句极尽渲染了清明时节悲伤的气氛,无奈之际只能借酒浇愁,而“遥指杏花村”却又是一幅春意如画的景象,在悲伤之中,却又对未来充满希望。而歌中这两句,傍晚时分,牧童归家,笛声悠扬,斟上一盏黄酒,点上几碟小菜,是何等惬意。青山环绕,江水蜿蜒,泛舟江渚之上,渔舟唱晚,夕阳余辉映衬爱人的脸庞,美不胜收,叫人如痴如醉。而在这美好的画面背后却藏着淡淡的哀愁,而如今,却只能自斟自饮,徒有无限美的夕阳,也只能感叹黄昏已近。

摹本易写 而墨香不退与你同留余味
一行朱砂 到底圈了谁

真迹已绝,虽只有摹本,但墨香却也能流传万世,就如同你,虽已难觅踪影,但你的一颦一笑却会在我心中隽永流长。这一行一行的文字,究竟是在向谁诉说呢?

无关风月 我题序等你回
悬笔一绝 那岸边浪千叠

欧阳修有词:“人生自是有情痴,此恨无关风与月。”传说乾隆下江南,夜游西湖湖心亭,被美景吸引,于是写下“虫二”二字,意为“風月无边”。我曾游览西湖,虽未能登上湖心亭一游,但舟行湖面,已足以让人心旷神怡,流连忘返。爱人不在,“应是良辰好景虚设”,纵有这无边风月,也已无心欣赏。悬笔落款,情到深处,心潮如岸边千叠的浪般澎湃。

情字何解 怎落笔都不对
而我独缺 你一生的了解

“问世间情为何物?”心中有千言万语想要述说,下笔时却又不知从何说起,仿佛任何词藻都不足以表达这份感情。正是“相思树下说相思,思郎恨郎郎不知。”

弹指岁月 倾城顷刻间湮灭
青石板街 回眸一笑你婉约

秦观有词:“韶华不为少年留,恨悠悠,几时休。”纵使有“一顾倾人城,再顾倾人国。”的容颜,也逃不过岁月的侵袭。而我却铭记在江南古镇,你的回眸一笑。“人生若只如初见,何事秋风悲画扇?”

恨了没 你摇头轻叹谁让你蹙着眉
而深闺 徒留胭脂味

李白有诗:“美人卷珠帘,深坐蹙娥眉。但见泪痕湿,不知心恨谁。”从“弹指岁月”到“徒留胭脂味”,这四句似在讲述民间女子被帝王选妃入宫,却冷落于后宫之中,只得与胭脂为伴的悲惨遭遇。

人雁南飞 转身一瞥你噙泪

“碧云天,黄花地,西风紧,北雁南飞,晓来谁染霜林醉?总是离人泪。”

掬一把月 手揽回忆怎么睡

夜色寂静,一缕月光洒在床头,回忆泛滥,辗转反侧,难以成眠。又似“长天净,绛河清浅,皓月婵娟。思绵绵,夜永对景,那堪屈指,暗想从前。”却只怨“明月不谙离恨苦,斜光到晓穿朱户。”

又怎么会 心事密缝绣花鞋针针怨怼
若花怨蝶 你会怨着谁

“自古多情空余恨”,多情本易自扰,才换来如今的无尽怨怼,只可惜“天长地久有尽时,此恨绵绵无绝期。”
“翻阶蛱蝶恋花情”,《蝶恋花》词牌多用来抒写缠绵悱恻之情,而其中数柳永,欧阳修和苏轼的几首最为有名。若花怨蝶,你又怨谁?“馀音落蕊坐相催,可怜绝世为谁媒。”

雨打蕉叶 又潇潇了几夜

《青花瓷》中也有芭蕉和雨,是我最喜欢的词句,“帘外芭蕉惹骤雨,门环惹铜绿。”一个“惹”字勾勒出了轻柔浪漫的画面。而此处的“打”字却平添了几分沧桑之感,“风又飘飘,雨又萧萧。……流光容易把人抛,红了樱桃,绿了芭蕉。”正如“山河破碎风飘絮,身世浮沉雨打萍。”

我等春雷 来提醒你爱谁

到最后,我也只能等待,待来年滚滚春雷敲醒大地之时,你会爱我吗?

2008年12月3日星期三

Liar Paradox and Halting Problem

Liar Paradox
首先来看一下什么是liar paradox。Liar paradox的雏形来自于一个叫作Epimenides的克里特人,他说:“所有克里特人都是说谎者。”这常常被认为等同于liar paradox,其实不然,因为如果他说的是真话,那么他就在说谎;如果他在说谎,说明至少还有一个克里特人说的是真话,这并不矛盾。最早的liar paradox版本是由生活在公元前4世纪希腊的麦加拉学派(Megarian)哲学家Eubulides提出的。他说:
A man says that he is lying. Is what he says true or false?
如果他说的是真的,那么他在说谎;如果他说的假的,那么他没在说谎。

后来又出现了一些liar paradox的变种,比较有名的有:
This sentence is false.
下面的悖论又名"Jourdain's Card Paradox":有一张卡片,一面写着:
The sentence on the other side of this card is true.
而在卡的另一面却写着:
The sentence on the other side of this card is false.

Halting Problem
Halting Problem是计算理论中的经典问题。它的意思,简而言之就是:对于一段程序和有限的输入,决定其是终止还是永久运行。说白了就是设计一个算法来判断一段程序是否会进入死循环。Alan Turing在1936年证明了不存在这样的算法。

在SICP课程的最后一讲[4]中,为了说明不是任何事物都是可计算的,使用了这样一个例子来证明不存在halting problem的算法。

首先,假设存在这样的算法,则存在函数(halts? p):
(halts? p)
=> #t if (p) terminates
=> #f if (p) does not terminates
现在有如下程序:
(define (contradict-halts)
    (if (halts? contradict-halts)
        (loop-forever)
        #t))

(contradict-halts)
=> ???????
很容易,我们可以定义(loop-forever)为:
(define (loop-forever)
    ((lambda (x) (x x))
    (lambda (x) (x x))))
这就是著名的Y combinator。

这里使用的就是liar paradox的精髓,如果这段程序会终止,则让它无限循环;如果程序不终止,则让它返回#t。

Resources
[1]. Liar paradox on Wikipedia.org
[2]. Some paradox
[3]. Halting problem on Wikipedia.org
[4]. Video courses of SICP

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

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