3.1 defun

在 Lisp 中,像 mark-whole-buffer 这样的符号会绑定一段代码,用于告诉计算机在调用该函数时执行什么操作。这段代码被称为函数定义(function definition),通过对以符号 defun(是 define function 的缩写)开头的 Lisp 表达式求值来创建。

在后续小节中,我们会查看 Emacs 源码里的函数定义,例如 mark-whole-buffer。本节先介绍一个简单的函数定义,让你了解其基本结构。这个例子使用算术运算,因为它足够简洁。有些人不喜欢算术示例,不过不必担心,本入门教程后续几乎不会再涉及算术或数学相关代码,示例大多围绕文本操作展开。

在关键字 defun 之后,一个函数定义最多包含五个部分:

  1. 要绑定函数定义的符号名称。
  2. 传递给函数的参数列表。如果函数不需要参数,该列表为空列表 ()
  3. 描述函数的文档字符串。(技术上可选,但强烈建议编写。)
  4. 可选的交互式声明表达式,让函数可以通过 M-x 加函数名调用,或绑定到快捷键/组合键使用。
  5. 指示计算机执行操作的代码,即函数定义的函数体

可以把函数定义的这五部分看作一个模板,每个部分对应一个位置:

(defun function-name (arguments...)
  "optional-documentation..."
  (interactive argument-passing-info)     ; optional
  body...)

下面举一个将参数乘以 7 的函数代码为例。(该示例非交互式,see 让函数支持交互。)

(defun multiply-by-seven (number)
  "Multiply NUMBER by seven."
  (* 7 number))

该定义以左括号和符号 defun 开头,后跟函数名。

函数名后面是包含函数参数的列表,称为参数列表(argument list)。本例中列表只有一个元素,即符号 number。调用函数时,该符号会绑定到传入的参数值。

参数名不一定非要用 number,也可以选其他名称,比如 multiplicand(被乘数)。用 “number” 是因为它直观地表明参数类型;用 “multiplicand” 则更能体现其在函数中的作用。当然也可以随便起一个无意义的名字如 foogle,但这会降低可读性。参数名由程序员决定,原则是清晰易懂。

实际上,参数列表中的符号可以任意命名,即使与其他函数中的符号重名也没关系——参数名只在当前函数定义内有效,属于局部名称。这就像你在家里昵称 “矮个子”,家人说这个名字指的是你,但在电影里同名角色指的就是另一个人。由于参数名是函数私有的,在函数体内修改它不会影响函数外部的同名符号,效果与 let 表达式类似。(See let。)

参数列表之后是描述函数的文档字符串。使用 C-h f 加函数名查看帮助时,显示的就是这段内容。顺便提醒,文档字符串的第一行最好写成完整句子,因为 apropos 等命令只会显示多行文档的第一行。此外,如果文档字符串有多行,第二行不要缩进,否则在 C-h fdescribe-function)中显示会很奇怪。文档字符串虽然可选,但非常有用,几乎所有函数都应该加上。

示例的第三行是函数体。(当然,大多数函数的定义会比这更长。)该函数的函数体是列表 (* 7 number),含义是将 number 的值乘以 7。(在 Emacs Lisp 中,* 是乘法函数,+ 是加法函数。)

使用 multiply-by-seven 时,参数 number 会被求值为实际传入的数字。下面是调用示例,但暂时不要尝试求值:

(multiply-by-seven 3)

函数定义中声明的符号 number,在实际调用时会绑定到值 3。注意,虽然 number 在函数定义中写在括号里,但调用 multiply-by-seven 时传入的参数并不需要括号。函数定义中的括号只是为了让计算机区分参数列表的结束位置和函数体的开始位置。

如果现在直接求值这个调用表达式,大概率会报错。(不妨试试!)原因是我们只写了函数定义,还没有把定义“告知”计算机——也就是还没有在 Emacs 中加载这个函数。安装函数的过程就是让 Lisp 解释器获知函数定义,下一节会详细说明。