Tchaikov’s Journal

January 21, 2008

Sun is releasing SunPinyin’s source code under a dual-licensing scheme of CDDL and LGPLv2.1

太高兴了,啥都不说了。


http://mail.opensolaris.org/pipermail/g11n-dev/2008-January/000027.html

January 9, 2008

6.001 的最后一课,一个时代的终结

Filed under: Programming, Tech

考研之后的一天,在贵阳紫林庵书店神使鬼差地买了本 SICP,之后便开始学习这本乍看之下怪怪的书。没想到短短几年之后,老大们开的课却已经到了最后一节

不由觉得时间过得真快呵,一年一年地过去,我们目睹了这么多世事变迁,有种看到大海潮水涌动,沧海桑田的感觉……

July 3, 2007

关于智能拼音输入法

前段时间和一位同学聊起工作上的事,知道了他在做 TTS (Text To Speech)相关的研究。谈话间说起了 SCIM 的智能拼音输入法,他觉得可以结合自己的研究工作来改进这个输入法,我也曾经有过类似的想法,读研时还读过一部分 scim-pinyin 的源代码。于是一拍即合,两人想着是不是能够在这方面合作改进这个软件。还定了一个简单的计划。不过我知道,一个项目最难的是出现一个原型, 要走的路还很长,评估平台,语料的收集,算法的 survey 都是问题。

我的想法是先了解一下各家开源的拼音输入法的算法,再作打算。上两周通读了 scim-pinyin 之后,把其中的算法给抽象出来了。但是光看一个算法,看不出门道。后来在读了一些关于统计语言模型的论文后,知道了 scim-pinyin 使用的也是 SLM,而且是基于 uni-gram 和 bi-gram 的模型。前两天了解到 Sun 也开发了自己的拼音输入法,同样基于 SLM,叫做 SunPinYin。而且 开源了!真是太好了。不过按照习惯,SunPinYin 的 license 应该是 CDDL。而 CDDL 与 Debian 的 DFSG 不兼容,因此就算有人打包和移植,也很难进入 Debian。

做了这些之后,有了些想法。更多的是觉得见识太少,现在要做的就是再看一两个智能拼音输入法的实现,啃几篇论文。然后想办法把评估平台搭起来,接下来就能非常容易地验证自己的想法了。

June 8, 2007

Common Lisp 笔记: defmacro once-only

Filed under: Programming

这两日在看 Practical Common Lisp。书相当不错,其中关于 macro 的讲解让我对 Lisp 的 macro 有了更多的了解。以前读 R5RS 的时候,每看到自定义宏总是当它是洪水猛兽,避之不及。一来是因为从来没有自己进行过语法层面的抽象。就像突然别人塞到手里一件宝贝,却完全不识货,难怪会心生畏惧。二来是不知道为何会有这样的需要,竟然要自己定义语法。习惯了让程序操纵数据,却不知道还可以让程序来编写程序。现在稍微好些,但还没有想得特别通透。

在书中举了一个例子,初看之下有点晕:

(defmacro once-only ((&rest names) &body body)
  (let ((gensyms (loop for n in names collect (gensym))))
    `(let (,@(loop for g in gensyms collect `(,g (gensym))))
       `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
	  ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
		,@body)))))

如果把

(once-only (foo bar)
    '(1 2 3))

展开,就是

(LET ((#:G1613 (GENSYM)) (#:G1614 (GENSYM)))
  `(LET ((,#:G1613 ,FOO) (,#:G1614 ,BAR))
     ,(LET ((FOO #:G1613) (BAR #:G1614))
        '(1 2 3))))

这里的 foo 和 bar 分别是两个将来的外部宏(即调用 once-only 的宏,比如书中的 do-primes)所使用的形参,它们对应的实参很可能是一些不希望被多次调用的表达式(如 (random 10) )。

once-only 的目的只有一个,让

(defmacro do-primes ((var start end) &body body)
  (once-only (start end)
    `(do ((,var (next-prime ,start) (next-prime (1+ ,var))))
	 ((> ,var ,end))
       ,@body)))
	
(do-primes (i 1 (random 10)
  (print i))

被展开成类似

(LET ((#:G1653 1) (#:G1654 (RANDOM 10)))
  (DO ((I (NEXT-PRIME #:G1653) (NEXT-PRIME (1+ I)))) ((> I #:G1654))
    (PRINT I)))

的语句。要完成这个任务, once-only 要做两件事情:

  1. 绑定两个新变量,变量名由 (gensym) 得出,让它们的值为实参元素 start 和 end 分别 evaluate 得到的结果。
  2. 把 do-primes 的 body 中所有出现 start 和 end 的地方都替换为这两个新变量名。

与其复杂的假象相反,其实, once-only 相当直截了当地完成了这两件事。

  1. (let ((gensyms (loop for n in names collect (gensym))))
      `(let (,@(loop for g in gensyms collect `(,g (gensym))))
    

    在这里,定义了 len(names) 个匿名变量,它们的值都是 gensym 生成的。虽然这些变量的变量名也是 gensym 生成的,但是我们所关心的并不是它们的名字,而是它们的值。这两句话在经过 once-only 和 do-primes 两次展开后就不复存在了,第一次展开会吃掉第一行语句,第二次则会吃掉第二行语句,它们留下的只有 gensyms 这个列表中每个元素对应的值,它们才是前面提到的“新变量”的变量名。

  2. ``(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
    

    为了看得更清楚一些,我把外面的 backquote 加上了。这句话是会留在展开后的 do-primes 里的。它把 names 中的元素 n 所 evaluate 出的值绑定到了新的变量名上。

  3. 现在只有一件事了,让 do-primes 使用新的变量名而非 start 和 end。

    ``(...... ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
    

    这里干脆把新变量名绑定到了老变量名上,这样处理的话,在展开 do-primes 宏的过程中,所有的老变量名就都会被替换成新的名字。是的,这句话也不会出现在二次展开的最后结果中。

好了,总算解释完了。咦,我怎么突然有种似曾相识的感觉?

November 19, 2006

MIT 不用 Scheme 了

Filed under: Programming

LtU 上看到的,真让人感觉诧异。MIT 在 EECS 的基础课 6.001 里通过介绍 Scheme 来教授程序设计。但是今天 Harold Abelson 却告诉大家,他们要把 Scheme 换成 Python!

世道变了?也许就像某人说的,Python 成了新的 Basic?

September 12, 2006

C++表达式中的副作用

Filed under: C++

昨天,有同学给我看这样的程序:

int fun(int n)
{
    if(++n==5)
	return n++;
    else {
	return n*fun(n++);
    }
}

问,

fun(1)
得多少?咋一看,这不是求 5! 么?再看,
++n==5
已经让 n+=1 了,那就应该是这样的:

f(1)
 2  *  f(2)
        3  *  f(3)
               4  *  f(4)
                       5

答案应该是 120。作为一个悲观的 code warrior,我没有就此停止,我习惯性地把段代码贴进 Emacs,编译了一把,运行结果竟然是……300。我瘫坐在椅子里,听着外面淅淅沥沥的雨声。不是说“*”号是从左到右么?怎么会这样。再看了一眼 K&R 的 TCPL,第53页里表格说的是 associativity,不是 evaluate order。而且就在这一页,作者提到了

C, like most languages, does not specify the order in which the operands of an operator are evaluated.

函数参数的情形也一样。

C++标准 ISO/IEC 14882-2003 chap5.4 也提到

Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.

Unspecified behaviour 和 undefined behaviour 真是 C/C++ 程序员的噩梦。故事如果发生在不使用副作用的函数语言(比如 Scheme)那里,应该就没有这种问题了。

July 26, 2006

IPython 和 Emacs (续)

Filed under: Emacs, Python

上次对 Liu Jin 的 ipython.el patch 的修改被 Fernando 接受了。最新的版本在这儿可以找到。

July 24, 2006

CS:APP 你还记得多少?

Filed under: Programming

CS:APP是上个学期读的。花了一个学期的功夫看完了。但是我刚才发现我把它给忘了!

昨天在看巴别塔上的雇工上关于 GNU/Linux 系统上关于运行时链接的一篇How PLT Works。觉得挺对胃口,操起 Emacs 比划了两下。突然有似曾相识的感觉。咦?CS:APP!从书堆里翻出它一看,原来都在里面呢。唉……

不过在自己操练的时候,有一点要注意的。先记下来:在用 gdb 调试 shared object 的时候,为了让 gdb 能不出类似 “Error while mapping shared library sections: libtest.so: sucess” 的错,最好能:

export LD_LIBRARY_PATH=/path/to/so-files:$LD_LIBRARY_PATH

而且新版的 gcc 产生的代码和文中 Red Hat 所带 gcc 产生的代码还颇不同,主要体现在 foo():

(gdb) disass
Dump of assembler code for function foo:
0xa7fe3510 :     push   %ebp
0xa7fe3511 :     mov    %esp,%ebp
0xa7fe3513 :     push   %ebx
0xa7fe3514 :     call   0xa7fe3505 <__i686 .get_pc_thunk.bx>
0xa7fe3519 :     add    $0x1187,%ebx
0xa7fe351f :    sub    $0x14,%esp
0xa7fe3522 :    mov    0xfffffff0(%ebx),%eax
0xa7fe3528 :    mov    (%eax),%eax
0xa7fe352a :    mov    %eax,0x4(%esp)
0xa7fe352e :    lea    0xffffef0e(%ebx),%eax
0xa7fe3534 :    mov    %eax,(%esp)
0xa7fe3537 :    call   0xa7fe340c

0xa7fe353c :    add    $0x14,%esp
0xa7fe353f :    xor    %eax,%eax
0xa7fe3541 :    pop    %ebx
0xa7fe3542 :    pop    %ebp
0xa7fe3543 :    ret
0xa7fe3544 :    nop

__i686.get_pc_thunk.bx是干什么的?

(gdb) disass 0xa7fe3505
Dump of assembler code for function __i686.get_pc_thunk.bx:
0xa7fe3505 <__i686 .get_pc_thunk.bx+0>:  mov    (%esp),%ebx
0xa7fe3508 <__i686 .get_pc_thunk.bx+3>:  ret
0xa7fe3509 <__i686 .get_pc_thunk.bx+4>:  nop

原来上面的函数功能相当简单,就是利用call命令的副作用,把下一个指令的地址压栈,再从栈顶取出,放到%ebx里。接着加上 0x1187 的偏移量,算出 GOT 的位置。下面就是例行公事一般地给 printf 分配 0x14 的栈空间。再根据 xyz 的地址在 GOT 上的偏移量(-0xF)和"OK %d\n"字符串的地址在 GOT 上的偏移量(-0x10F1),分别算出 xyz 的值和格式化字符串的地址,先后压栈。调用 0xa7fe340c。细心的同学可能注意到了,printf@plt。说明 printf 也是在 PLT 上的。的确,printf 是 libc 的一员,a.out 动态地调用它,这样可以省一些存储空间,这和调用 foo 用的方法同出一辙。。

通过这个手段给 %ebx 赋值完毕以后,我们检查一下两个参数:

(gdb) x *($ebx+0xfffffff0)
0x8049710 :        0x00000004
(gdb) x/s $ebx+0xffffef0e
0xa7fe35ae <_fini +26>:   “OK %d\n”

在 PLT 的 foo 表项中,

(gdb) disass
Dump of assembler code for function foo@plt:
0x080483ac : jmp    *0x8049700
0x080483b2 : push   $0x10
0x080483b7 :        jmp    0x804837c <_init +24>
End of assembler dump.

和文中导致段错误的 disass 不同,在我这里根本就无法反汇编到 0x804837c 地址的代码:

(gdb) disass 0x804837c
No function contains specified address.

难免有遗珠之憾。

本以为不必专门写一个 foo() 函数的,只要 printf 两次就可以探究 GOT 和 PLT 的奥秘。因为 printf 本身就是在 PLT 上的。但是,当我第二次进入 printf 的时候发现,它进入的 printf 实现代码里,没有看到 PIC 函数(即 printf 自己)引用外部数据的代码(即__i686.get_pc_thunk.bx)。另外,在调用 printf 时不能一次加“/n”,另一次不加。因为 gcc 会把加上“\n”的 printf 翻译成 puts@plt,把不加的翻译成真正的 printf@plt,否则两次调用都会写 GOT 中的相应表项。

July 9, 2006

为什么 Python 没有 /* */

Filed under: Python

熟悉一门计算机编程语言的朋友,在学习新的编程语言时,都会自觉或不自觉地希望在新的语言中找到原来那门语言中对应的元素。很自然地,学过 C 的同学,在看待 Python 时,也会这样做。

初看之下,Python 里缺少块注释(block comment)。Python 语言里用#来标志一行里,注释的开始。如果要多行注释?这简单,每一行都用“#”打头不就得了。

有的朋友就感到很困惑,有的朋友觉得这样很累,这几篇 post 已经有好几年历史了。但是,由它带出的一些思考在今天还是有它的意义的。thread 中的发言可以分作两派:

  • 一派就事论事,提供了一些不错的解决办法
  • 用“#”注释 code block 的每一行
  • 把 code block 放在if 0: ... 的缩进块里
  • ”“”或者'''的 triple quotes 把要注释的代码包起来。不过这种办法必须要注意 triple quotes 的缩进,必须服从上下文。另外,被注释的代码不能含有 triple quote(s)。
  • 因为任何像样的编辑器都应该有块注释的功能。就算它没有,也应该能够进行配置,使它能容易地帮助用户加入希望的注释标记。Emacs: C-c # 就是块注释,C-u C-c # 就是取消块注释
  • vim 也有很多办法,可以帮我们注释大块的代码。用 google 大仙能找到大把的方案。
  • 另一派则试图说服楼主,让他理解,每一行都用“#”自有它的好处,而块注释标记则有其让人难堪的时候
    • 如果使用“/**/”来注释大块的源程序,对于浏览代码的程序员来说,看到一行代码就无法快速的知道,这行代码到底是不是注释,只能让程序员“上下而求索”了。不过这个问题对于有良好的语法着色的编辑器来说是不成问题的。
    • 而且“/**/“没有办法包含另一个”/**/“ block。相信大家在写 C++程序的时候肯定碰到过很多这种情况吧。因为里面的“*/“会和外面的”/*“配对,使里面的“*/”之后的代码没有被注释起来。
    • 而且,对于各种没有语法分析功能的文本分析程序(比如 grep,简单的 perl 程序)来说,“/**/”的出现对他们统计和处理代码都带来了挑战。它们无法仅仅根据一行是不是“#”(对于C/C++是“//”)开头的,来判断这一行是不是注释。

    看完两边的发言。掩卷沉思。有两个结论:

    1. 嗯,C++ 引入“//”的确是有它的理由啊。
    2. 生子当如孙仲谋,写程序当用 Emacs/VIM。
    附带着说一句,Emacs 里 python-mode 的缩进可以用 C-c > 和 C-c < 来成块地控制。

    July 5, 2006

    Python 的点点滴滴——?:

    Filed under: Python

    Python 可以用 [false_exp, true_exp][bool_exp] 的方式模拟 C 语言里的a?b:c三目操作符。对于那些不在乎没有被取值,但是也同样被 evaluate 的 {true|false}_exp 来说,是相当合适的。

    但是今天碰上了一件怪事,是在运行那个用 wxPython 写的演示程序时,Python2.3 说:

    return (val/2, [0,1][val > self.upper or val < self.lower])
    TypeError: list indices must be integers
    

    开始的时候打开 ipython (Python2.4)又试了一下,好的啊。再用 Python2.3 试,也没问题。晕菜了,把[0,1][val > self.upper or val < self.lower]改成[0,1][int(val > self.upper or val < self.lower)]。终于闭嘴了,清静了。不过还是不知道他为什么会这样。

    Get free blog up and running in minutes with Blogsome
    Theme designed by Jay of onefinejay.com