Random Tech Thoughts

The title above is not random

LispCast – More Lisp Movies

又有热心的 Lisp 程序员制作开发录像了,LispCast 这个 blog 将发布一系列的 Common Lisp 开发录像,作者也将发布一些 Common Lisp 相关的文章。这篇 post 是帮作者宣传一下,也算是对这个项目的一点贡献吧。

从 about 页面里抄段介绍来:

  1. To enhance the code quality and clarity of existing Lisp programmers.
  2. To introduce Common Lisp to those programmers who are interested.
  3. To gain acceptance of Common Lisp as a modern, relevant language in the programming community.

看 Concrete Mathematics 时对教科书的一点感想

看 Concrete Mathematics 的时候发现,国内的数学教材跟它比真是枯燥无聊乏味,数学书如果能像 CM 写的那么有趣的话我的数学就不会学那么差劲了。

我想很重要的一点在于观念,干什么非要把学习过程搞的那么痛苦无趣难以让人喜欢?就因为要把书写的正式一点,看起来像是专家写的?想起 Knuth 说的,如果书是为写给专家看的,那就没有人能够看懂。他和 CM 的其他作者在 CM 的前言里面还说,他们觉得数学是有趣的,他们要将他们在数学工作中的快乐和痛苦都在书中展示出来,他们在书中使用了一种非正式风格。学习的过程是艰苦的,但是其中也应该是充满乐趣的。

之所以把书写的那么无趣的另一个原因是作者没有考虑人学习的过程,没有想过怎样写书才能使读者学习起来更容易,更有效,而只是简单的把要传授的内容写下来。这一点 HeadFirst 系列的书是考虑用户学习过程的典范。当然,所有人都那么写书的话也太浪费了。这对作者的要求其实很高,得懂些心理学和教育方面的东西才行,另外要想着读者,为读者着想。(现在越来越觉得教育学很重要。)

国内的教材的另外一点不足是只罗列知识点、概念,而没有展示解决问题的过程,教人解决问题的方法。就比如说国内的数学书,基本上都是给出些定义,证明些定理,给几个例题看看怎么应用定理。(这样单调的方式是造成书无聊的一个方面)

看看 CM 里面是怎么做的,拿第一章讲 Joseph 问题的例子来看,CM 里面从问题的典故开始,抽象问题以使用数学方法来解决问题,最后再分析、泛化结果。整个过程中体现了解决问题的方法,先观察简单的情况,根据简单情况来猜测、归纳解,再证明结论,得到结果后进行泛化以更好的了解问题,并使结果能够适用更广泛的问题,不时还来上一些幽默。这个解决问题的过程国内的教材很少有,对解进行泛化的也很少见。

想起 SICP 了,书里面给出的例子好坏也很重要,SICP 在这方面就做的非常好。一本讲计算过程的书,给的例子有牛顿迭代法,蒙特卡罗方法,费马小定理,Miller-Rabin 素数检验,Lamé 定理,图形处理中的分形,数字电路的模拟等等,非确定性计算。光是这些例子就很让人获益了,而这些例子也使这本书非常有趣。要能给出如此有广度的的例子,作者自己得有非常广泛的涉猎。记得有本 Thomas Calculus,讲微积分的超级厚一本书,已经第十版了,里面对微积分的应用采用的例子有物理方面的,还有经济方面的。学校图书馆里面有一本,发现这本书的时候已经学完大数了,所以没有借了看。

当然,我看过的教材也只有通修课和我专业课那些,通修课的教材都是国内的,专业课的全是国外的教材,得出上面想法的参照面比较狭窄。国内也有好的教材,不过我没有看过;我们选的专业课教材一般也都是比较经典的,所以国外的烂教材也没有看过。

另外我是那种很不喜欢逼自己做事的人,只有喜欢的书才会好好去看,所以一本书如果写的无聊的话就会被我丢掉,所以也可能把有些好书给忽略了。上面的看法很大程度就是因为我这样的看书特点而发的。

SLIME Movie

使用 SLIME 的极好的教学电影,制做人 Marco Baringer 。电影介绍了使用 SLIME 进行代码编辑,跟踪函数调用,调试、测试,用 asdf-install 安装库,查看文档等许多方面,同时也可以看看 Lisp 程序员是怎么写代码的。(这样的 IDE 用起来真的是很爽!)

Bill Clementson 的 Blog 找到的。给个下载链接吧。 http://common-lisp.net/movies/slime.mov http://common-lisp.net/movies/slime.torrent

(这两位都是 Mac 的用户,那个电影里面桌面就是 Mac,Lisp 用的是跑在 Linux 机器上的 SBCL,通过 ssh 连接过去的。Macro 另外一个关于 UCW 的电影则直接用的 Mac 上的 OpenMCL。)

Macro Baringer,他是 UnCommon Web 的作者, UCW 是一个基于 continuation 的 Web 开发框架。这样的 Web 框架最成功的应该是 Seaside 了,使用的语言是 Smalltalk。我目前对 Web 开发还没多少经验,所以看他们介绍使用 continuation 进行 Web 开发的好处没有概念,就不多罗嗦了。

交换 Caps Lock 和 Esc

Vim 里面你怎么按 Esc 键?今天网上看到有人开玩笑说用巴掌拍过去,这个似乎暴力了点……

我是用左手小拇指按的,很神奇吧,那么远居然用小指按。

如果你看过 Vim Tips 的话可能就会知道怎么回事。其实我按的是 Caps Lock,利用 xmodmap 修改了 X window 下的键盘布局。因为我很少用到 Caps Lock 键,同时也为了方便按键,我交换了 Caps Lock 和 Esc 的位置。上代码,

remove Lock = Caps_Lock
keysym Escape = Caps_Lock
keysym Caps_Lock = Escape
add Lock = Caps_Lock

保存到某个文件,比如 ~/.Xmodmap,然后执行 xmodmap ~/.Xmodmap 就可以了。当然,我现在是每次启动 X 的时候都自动执行这条命令。开始会有些不习惯吧,但习惯以后肯定可以提高使用 Vim 的爽快感的。

Emacs 也有用户交换 Ctrl 和 Caps Lock 的,但个人感觉那样的话按 C-x 不是很不爽……

SLIME & Encoding

While I was experimenting “cating” a file in Common Lisp, I found that SLIME will report lost connection once I read an UTF-8 encoded file. For plain ascii file, however, there’s no problem. I tried to run the program directly in SBCL, it also worked fine. So I was sure that this problem was caused by SLIME.

After Googleing for a while, I came to this soluiton. Just put this in your .emacs file.

(require 'slime)
(setq slime-net-coding-system 'utf-8-unix) ; Add this line.
(slime-setup)

The setq tells SLIME to use the specified encoding. And now reading UTF-8 encoded file has no problem, and you can also input Chinese characters into SLIME.

For more information, follow this thread in the slime-devel mailing list.

Libglade and Gtk2 Signal Connecting

I decided to write this post in English. The reason of writing in English will be explained at the end of this post.

I’ve just started learning gtk2 and glade to build GUI applications. After a little experiment, I quite liked the way libglade create the GUI on the fly. No compilation is needed when changing the layout of the user interface, and dragging widgets to create GUI application is much less tedious than writing code manually. But the way libglade connect signals and handlers really caused me lots of confusion and trouble.

I assume you understand gtk2 and are family to glade. You know we can use g_signal_connect to connect a signal and handler together, so when the signal is emitted, the handler will be called. Here’s an example,

void foo(GtkWidget* wideget, gpointer user_data);

g_signal_connect(G_OBJECT (object),
           "some_event",
           G_CALLBACK (foo), pointer_to_data);

If the callback function requires only one argument, you can just pass NULL as the 4th argument to g_signal_connect. After the signal and handler being connected, when some_event is emitted on the object, the callback function foo will be called like this,

foo(object, pointer_do_data);

So the callback function can know which widget emitted the signal, and get additional data from pointer_to_data. This is quite natural, nothing surprise. But there is another function g_signal_connect_swapped can be used like this,

void bar(gpointer data, GtkWidget* wideget);

g_signal_connect_swapped(G_OBJECT (other_object),
                     "another_event",
                     G_CALLBACK (bar), pointer_to_data);

When another_event is emitted on other_object, bar will be called like this,

bar(pointer_to_data, other_object);

See the difference? The argument passed to the callback function is swapped!

What’s the use for g_signal_connect_swapped?

There are some functions in the gtk2 library that accepts only one widget as the argument. And please note that these function works on the widget passed to them. For example,

void gtk_widget_destroy(GtkWidget *widget);

Now, if you want to destroy the widget which emitted some signal itself, you can use g_signal_connect without problem. The connection and function call is shown in the following code.

g_signal_connect(G_OBJECT (object),
             "some_event",
             G_CALLBACK (gtk_widget_destroy), NULL);

/* When the signal is emitted. */
gtk_widget_destroy(object);

However, when you want to destroy a widget that’s not the widget emitting the signal, you won’t be able to do it with g_signal_connect. This is because that the widget passed to the callback function is always the widget emitting the signal. See the problem? That’s how g_signal_connect_swapped comes into play.

g_signal_connect_swapped(G_OBJECT (other_object),
                    "some_event",
                    G_CALLBACK (gtk_widget_destroy),
                    target_object);

/* When the signal is emitted. */
gtk_widget_destroy(target_object);

I guess other_object is also passed to gtk_widget_destroy, but there’s no harm as long as the first argument to the function is correct.

Usually, we use only g_signal_connect to connect signals and callbacks we defined. g_signal_connect_swapped is only used when connecting signals and gtk2 library functions like gtk_widget_destroy mentioned above.

How libglade connect signal and handler?

g_signal_connect and g_signal_connect_swapped works fine if I don’t use libglade to create GUI on the fly. The problem with libglade is that whenever you need to passed any data to the callback function, it will use g_signal_connect_swapped to connect the signal and the handler. This can be figured out from the source file of libglade, in the function autoconnect_foreach.

for (; signals != NULL; signals = signals->next) {
    GladeSignalData *data = signals->data;
    if (data->connect_object) {
        GladeXML *self = glade_get_widget_tree(
                                GTK_WIDGET(data->signal_object));
        GObject *other = g_hash_table_lookup(self->priv->name_hash,
                                               data->connect_object);

        g_signal_connect_object(data->signal_object, data->signal_name,
                func, other, (data->signal_after ? G_CONNECT_AFTER : 0)
                                | G_CONNECT_SWAPPED);
    } else {
        if (data->signal_after)
            g_signal_connect_after(data->signal_object,
                                   data->signal_name, func, NULL);
        else
            g_signal_connect(data->signal_object, data->signal_name,
                             func, NULL);
    }
}

Note how g_signal_connect_object is called. The swap flag is set! It’s effect is just like calling g_signal_connect_swapped.

The problem with that is whenever you want to connect your own callback function with any signal through glade and pass some data to the callback function, then glade_xml_signal_autoconnect will create the connection with the the arguments swapped. This is not the usual situation when you create the connection manually.

The solution

The ideal solution I suppose need to modify libglade and glade so they provide an option to the user whether the argument passed to the callback function should be swapped. But currently the option will not be available, we can only use alternative solutions to solve the problem.

The first solution is to change your callback function, swap the argument so the connection created by libglade will be correct. But when you decide not to use libglade to create the GUI and create the connection by yourself later, you should remember the function argument passed to the callback function should be swapped.

Another solution is just to create the connection manually. This requires a little more work since you can’t use libglade automatically create the connection then.

Either solution has its own drawbacks. I prefer the second one.

Why writing in English?

As said in my about page, I would write technical articles in English but I didn’t follow this rule strictly , this is because my.donews is not wordpress.com and has little foreign readers. However, this time I find the problem may have caused confusion to quite a few people as I didn’t find any answer through google, I think writing this article in English maybe more valuable. Besides, my English writing skills need more practice. There may be more English articles in my blog in the future.

用 ASDF-INSTALL 自带下载安装 Common Lisp 库

以前下载安装 Common Lisp 的库都是手工进行的,其实 ASDF 有个扩展 ASDF-INSTALL 可以自动从网上下载并安装 Common Lisp 的库,而且可以自动解决依赖性问题。

首先最好安装 gpg,安装过程中会通过 gpg 来进行验证。当然你可以选择跳过检查,但是一定要装上,否则安装过程中会因为没有 gpg 而出错。

对 SBCL 的话只需要

(require 'asdf-install)

就可以开始使用了。比如要安装 memoize

(asdf-install:install 'memorize)

ASDF-INSTALL 会首先问你安装到系统还是你的主目录下,之后它自动从网上找到这个库文件,然后下载、安装。安装过程中会提示是否进行验证,编译出错如何处理等问题。完成之后就一切 OK 了。

如果安装在主目录下的话,包文件是放在 ~/.sbcl/site 目录下,所有的 asd 文件都在 ~/.sbcl/systems 目录下建好软链接,把这个 systems 目录设置到 ASDF 的 central-registry 里就可以很方便的使用了。

现在有一个问题,在我这里 ASDF-INSTALL 对有些库并没有自动解决依赖性,不知道为什么。

Right Fold vs. Left Fold

Why functional programming matters(非常推荐这篇论文。) 时看到它给出的 reduce 与 common lisp 中提供的 reduce 不同。

这篇论文里面的 reduce 作用是这样的,对一个 list

(cons e1 (cons e2 (cons e3 ( ... (cons en nil)))))

reduce 接受两个参数,一个是接受两个参数的函数,把这个函数去替换 list 中所有的 cons 函数;另一个参数则替换 nil。举例来说,如果这样调用 (reduce add 0 list),则产生的效果是

(add e1 (add e2 (add e3 ( ... (add en 0)))))

也就是对 list 中的元素进行了求和。

这个 reduce 非常强大,可以用它方便的写出 append, map 等函数。(append a b) 即 (reduce cons b a),(map func list) 即

(defun map (fun list)
  (foldr
   (lambda (a b)
     (cons (funcall fun a) b))
   nil
   list))

因为 Common Lisp 中没有函数组合操作符,所以这个 map 没有在论文使用的语言 Miranda 那么优雅。

Common Lisp 中的 reduce 与上面描述的不太一样,初值要通过 key 参数 :initial-value 指定,写成这样 (reduce #‘add list :initial-value 0),而且它的默认行为也不太一样。一番搜索之后发现了这个操作通常称为 fold,Wikipedia 上 fold 条目 对这个操作有详细的解释,而且解释 fold 操作的两张图片也很直观。

fold 操作最方便的地方是使得一个原本只接受有限个参数的函数能够作用在元素个数不定的 list 上。

fold 操作分两种,上面说的那种被称为 right fold,而 Common Lisp 中的 reduce 默认是 left fold,C++ 中的 accumulate,Ruby 中的 inject 都是 left fold。Common Lisp 中的 reduce 加上 key 参数 :from-end t 以后其行为就跟 right fold 相同了。Paul Graham 的 Ansi Common Lisp 的 Appendix D 中对 reduce 的说明也很清楚,下面的例子就参考了他的说明。

对最开始的 list 例子用 left fold 操作以后的效果如下:

(add (add (add ( ... (add 0 e1) e2) e3) ... ) en)

简单来说,left fold 也接受两个参数,一个是接受两个参数的函数,另一个是初值。left fold 将初值和 list 中的第一个元素传给函数,然后将函数返回值和第二个 list 元素传给函数,一直这样下去直到 list 的最后一个元素 nil 之前。left fold 也可以轻易的实现求和等操作,当然对于不满足交换率的操作使用 left fold 和 right fold 得到的结果是不同的。但是要实现 append, map 这样的操作就无能为力了(可能也可以,但肯定很麻烦吧)。

Ansi Common Lisp 第 4 掌的第 2 条习题就是用 reduce 来实现 copy-list 和 reverse,只针对 list,不考虑 array。实现如下,

(defun copy-list (lst)
  (reduce #'cons  lst :from-end t :initial-value nil))

(defun reverse (lst)
  (reduce #'(lambda (a b) (cons b a)) lst :initial-value nil))

第一个用的是 right fold,非常直观,用 cons 替代原来 list 里面的 cons 就可以了,第二个用了 left fold,匿名函数只是交互了一下 cons 的两个参数而已,看下 right fold 的针对 add 的那个例子就很容易明白了。

Debian 名字的由来

Adam Leventhal 的 Blog 时,在他讲 Debian 的创始人 Ian Murdock 到 Sun 工作的那篇文章中跳到了 Ian 的 Blog 去了。

在他 Blog 的 about 里面他提到 Debian 其实是他妻子的名字和他自己名字的组合,他妻子叫 Debra。令我佩服的是 Ian 在 1993 年创建 Debian,而他在 Purdue 获得科学学士学位是在 1996 年,那个时候 Debian 就叫 Debian?如果是的话他和他妻子在他大学二年级就应该已经认识了。当然佩服的不是他们从大二就开始恋爱,而是他大二就已经开始创建一个 Linux 发行版了,而我大二才开始接触 Linux。(abcx 看到这句话又要狂笑了吧……)

Pride and Prejudice

《傲慢与偏见》一直都是我很喜欢的一部小说,今天看了 2005 版的电影,下午看了一遍,晚上又看了一遍。画面太美了,影片拍摄地的景色太漂亮了,音乐也非常棒,剧情当然也是没的说。饰演伊丽莎白的 Keira Knightley 演的也不错,漂亮而又优雅,神情中经常带着一丝调皮。这些元素组合在一起让我忍不住看完又看,即使在重看的时候也没有觉得无趣。一部电影看完马上重看在我好像还是第一次。电视剧的话只有东爱了。

马上找电影的原声录音来听。学小提琴以后就觉得钢琴比不上小提琴,听了电影中的音乐以后不再这么认为了,两者各有千秋。

下个学期要看原版小说。虽然上个学期就买了,可是感觉难度太大,看了个开头就没有继续。但这次看过电影以后激起的兴趣应该可以让我坚持下去了。Jane Austen,感谢你写出这样的伟大的小说 :–)