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
太高兴了,啥都不说了。
—
http://mail.opensolaris.org/pipermail/g11n-dev/2008-January/000027.html
前段时间和一位同学聊起工作上的事,知道了他在做 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。
做了这些之后,有了些想法。更多的是觉得见识太少,现在要做的就是再看一两个智能拼音输入法的实现,啃几篇论文。然后想办法把评估平台搭起来,接下来就能非常容易地验证自己的想法了。
这两日在看 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 要做两件事情:
与其复杂的假象相反,其实, once-only 相当直截了当地完成了这两件事。
(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 这个列表中每个元素对应的值,它们才是前面提到的“新变量”的变量名。
``(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
为了看得更清楚一些,我把外面的 backquote 加上了。这句话是会留在展开后的 do-primes 里的。它把 names 中的元素 n 所 evaluate 出的值绑定到了新的变量名上。
``(...... ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
这里干脆把新变量名绑定到了老变量名上,这样处理的话,在展开 do-primes 宏的过程中,所有的老变量名就都会被替换成新的名字。是的,这句话也不会出现在二次展开的最后结果中。
好了,总算解释完了。咦,我怎么突然有种似曾相识的感觉?
在 LtU 上看到的,真让人感觉诧异。MIT 在 EECS 的基础课 6.001 里通过介绍 Scheme 来教授程序设计。但是今天 Harold Abelson 却告诉大家,他们要把 Scheme 换成 Python!
世道变了?也许就像某人说的,Python 成了新的 Basic?
昨天,有同学给我看这样的程序:
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)那里,应该就没有这种问题了。
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 中的相应表项。
熟悉一门计算机编程语言的朋友,在学习新的编程语言时,都会自觉或不自觉地希望在新的语言中找到原来那门语言中对应的元素。很自然地,学过 C 的同学,在看待 Python 时,也会这样做。
初看之下,Python 里缺少块注释(block comment)。Python 语言里用#来标志一行里,注释的开始。如果要多行注释?这简单,每一行都用“#”打头不就得了。
有的朋友就感到很困惑,有的朋友觉得这样很累,这几篇 post 已经有好几年历史了。但是,由它带出的一些思考在今天还是有它的意义的。thread 中的发言可以分作两派:
if 0: ... 的缩进块里”“”或者'''的 triple quotes 把要注释的代码包起来。不过这种办法必须要注意 triple quotes 的缩进,必须服从上下文。另外,被注释的代码不能含有 triple quote(s)。看完两边的发言。掩卷沉思。有两个结论:
Python 可以用 [false_exp, true_exp][bool_exp] 的方式模拟 C 语言里的a?b:c三目操作符。对于那些不在乎没有被取值,但是也同样被 evaluate 的 {true|false}_exp 来说,是相当合适的。
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