按语:这是我在低矮的土坡上为「不懂编程的人」写的系列文章的第三篇,整理于此。它的前一篇是《第一声问候》,讲述了如何用 Emacs Lisp 写一个可以在 Emacs 计算机里运行的 hello world 程序。

我们已经通过一个极为简单的 hello-world 函数,向 Emacs 计算机发出了第一声问候。

这个 hello-world 函数的定义非常简单:

(defun hello-world ()
  (interactive)
  (insert "hello, world"))

但是一旦弄懂了如何使用 Emacs Lisp 语言定义一个函数,以及 interactiveinsert 的用法,我们就可以在很多时候从一个勤劳的人蜕变为一个懒惰的人。

此前,有一段时间,我经常用 POV-Ray [1] 渲染一些很简单的三维场景:我向 POV-Ray 提供场景文件,由 POV-Ray 使用光线追踪技术基于场景文件中的描述完成三维渲染。每份 POV-Ray 场景描述文件往往是以下面内容作为开始:

#version 3.7;
#include "colors.inc"

global_settings {
  assumed_gamma 1.0
}

一开始我比较勤劳,会在每份场景文件中很认真地手动输入上述内容。现在就可以不必如此,在 init.el 中像下面这样定义一个函数:

(defun povray-head ()
  (interactive)
  (insert
   "#version 3.7\n"
   "#include \"colors.inc\"\n\n"
   "global_settings {\n"
   "  assumed_gamma 1.0\n"
   "}\n"))

这样,每次当我新建了一份 POV-Ray 场景文件之后,只需要向 Emacs 发送 M-x povray-head <RET> 指令,场景文件中就会自动出现上述内容。

看,并不需要多么高超的编程技术,只是将 hello-world 函数略微改动一下,就节省了许多重复性的劳动。从物理学的角度来看,在我编写许多 POV-Ray 场景描述文件时,这个 povray-head 函数可以让我更省力。

所以,在我看来一个程序首先应该是一种有用的机械,它与杠杆、滑轮、斜面这些基本的省力装置不应该存在本质上的区别。因此,在写一个程序之前,需要明确,这个程序是否可以让你或让其他使用这个程序的人更省力。不省力的程序,是没必要去写的。

最近为了写几篇 Emacs Lisp 编程方面的文章,我不得不在 Emacs 里编程。实际上我也经常需要使用 C 语言在 Emacs 的宿主系统中编程。我经常需要写一些像下面这样的代码:

double *foo = malloc(n * sizeof(double));

若没学过 C 语言,就不必在意这行代码的含义,将它视为普通的文本即可。倘若手写这行代码,我必须写两次 double。现在,我可以再在 init.el 中定义一个 c-malloc 函数,让我在写此类代码时能够省点力。

以下是 c-malloc 的定义:

(defun c-malloc (name type n)
  (interactive
   (list (read-string "变量名称?")
     (read-string "类型?")
     (read-string "数量?")))
  (insert (format "%s *%s = malloc(%s * sizeof(%s));" type name n type)))

这个函数的定义,与 hello-world 以及 povray-head 不一样,它是带有参数的函数。与后两者相比,它更符合我们在数学中对函数建立的一般性认识,即 w = f(x, y, z) 或 f: (x, y, z) -> w。

暂时不考虑 c-malloc 的细节,先看它是如何工作的。

现在,倘若 Emacs 重新加载了 init.el 文件,那么使用 M-x c-malloc <RET> 指令便可让 Emacs Lisp 解释器对 c-malloc 进行求值,但是 Emacs 在执行上述指令后,会在微缓冲区里问我三个问题。

第一个问题是:

变量名称?

我(在微缓冲区里)输入指令 foo <RET>,就回答了这个问题。紧接着,Emacs 会再问:

类型?

我输入指令 double <RET>,就回答了这个问题。最后,Emacs 会问:

数量?

我回答 n <RET>

注:<RET> 表示单击回车键。例如 n <RET>,表示输入 n 之后,单击回车键。

回答了这三个问题之后,Emacs 就会在缓冲区里显现:

double *foo = malloc(n * sizeof(double));

实际上,Emacs 接受我的答案之后,会将它们依序分别绑定到 c-malloc 的三个参数 nametypen 上,这就相当于形成了下面的表达式:

(c-malloc "foo" "double" "n")

接下来,Emacs Lisp 解释器对这个表达式进行求值,结果就是在缓冲区里插入

double *foo = malloc(n * sizeof(double));

像这些琐碎但是出现频率比较高的代码片段,花一点时间,用编程的方式来生成它们,不仅省力,而且也避免了手工输入的错误。一个勤劳的人,应该用编程的方式让自己变懒,而不是让自己活成一段程序。

上文中出现的 Emacs Lisp 代码,看不懂没有关系,因为在下一篇就开始讲述 Emacs Lisp 的文本处理方面的编程知识。

下一篇原子与虚空


[1] http://povray.org/

你可能感兴趣的文章

载入中...
garfileo garfileo

4.7k 声望

发布于专栏

while(1) { }

只关心 C 与 Lisp/Scheme 程序的基本形状,其他的事关心不过来。

136 人关注