Emacs Lisp 学习(2)

昨天, 学习了 Emacs Lisp 的一些基本的概念, 今天学习如何定义函数, 如何使用函数. 在 Emacs Lisp 中有几种定义函数的方法, 今天学习的是使用宏 defun 来定义函数的方法.

本次学习涉及到的专业术语

首先我们介绍一些在这次学习中遇到的一些术语, 比如 , 特殊形式, 还有 原始函数 等几个概念.

首先我们来说一下原始函数. Lisp 解析器是一个可执行的程序, 这个程序是用别的语言实现的. Emacs Lisp 解释器使用 C 实现的, 在 Emacs Lisp 实现的时候, 为了执行效率或其他方面的考虑, 直接使用 C 语言实现的 Lisp 的基本操作函数, 称为原始函数. 在 Emacs 的函数描述文档, 你就能看到不少的 C 实现的 Lisp 函数, 比如 +. 我们按下 C-h f RET + 就能看到函数 + 的帮助文档:

+ is a built-in function in `C source code'.

(+ &rest NUMBERS-OR-MARKERS)

Return sum of any number of arguments, which are numbers or markers.

Emacs 的帮助文档中, 称为内建函数, 也就是我们说的原始函数. 内建函数, 或者原始函数, 和我们直接用 Lisp 语言定义的函数有什么区别吗? 在我们写 Emacs Lisp 程序的时候, 基本上是没什么区别的. 对于 Emacs Lisp 来说, 都是作为函数来使用的.

那么宏是什么呢? 宏其实是用 Lisp 语言写的特殊的函数. 其特殊之处就是 Lisp 不按照平常的函数调用规则来处理这种函数的调用。

最后我们来说说, 特殊形式, 形式(form) 在 Lisp 中其实可以和 s 表达式相互替换. 所以, 当我们说特殊形式的时候, 一般我们的意思就是 Lisp 对这中 s 表达式的的计算不是按照常规的作法来做的. 特殊形式有可能是内建函数, 也可能是宏来实现, 总之, 如果 Lisp 对她的调用和其他的表达的处理不一样, 那么 就是特殊形式. Lisp 和其他语言的很大的不同是, 其实 Lisp 解释器定义的关键字很少, 原因就是除了一些原始函数必须有 Lisp 自己实现外,大部分的特殊形式都可以用宏来实现. 特殊形式, 本质上定义了 Lisp 语言的语义. 因为定语语义的特殊形式大部分可以通过宏来实现, 所以定语语义的关键字就少了.

现在就暂时把把特殊形式, 理解为其他语言的关键字吧. 这次我们学习特特殊形式 defun, 定义函数的宏.

defun

Emacs Lisp 中 defun 宏用来定义一个函数, 具体的用法是:

(defun FUNCTION_NAME ARGUMENT_LIST
 OPTION_DOCSTRING OPTION_INTERACTION  FUNCTION_BODY)

在上面的代码模版中, 显然是一个 Lisp 合法的列表. 第一个元素是符号 defun. 第二个元素 FUNCTION_NAME 是一个符号, 这个符号表示函数的名字. 第三个元素 ARGUMENT_LIST 是一个列表, 其中的元素是符号, 表示函数的参数. 第四个是一个可选的,这个是一个字符串, 用来了描述这个函数的功能. 在 Emacs 中使用 C-h f function-name 得到的函数的帮助文档, 就是通过这个字符串生成的. 所以, 虽然是可选的部分, 仍然建议, 自己写 Emacs Lisp 函数的时候, 要写上文档字符串. 原来在学习 Python 的时候, 第一次接触到了文档字符的概念, 当时觉得 Python 的处理真是独特啊, 使用文档字符来生产随机的帮助文档, 额外的处理都不用, 比 Java 的文档注释高明多了. 学习 Emacs Lisp 才知道, 原来 Python 这个东西也是从这里借鉴的. 第四个元素 OPTION_INTERACTION 也是可选的元素, 这是一个列表, 是 interactive 调用. 我们后面讲. 第五个元素 FUNCTION_BODY 是任意多个的 s 表达式. 这是函数体. 函数执行的时候, 依次计算 FUNCTION_BODY 的每个表达式, 最后的一个表达式的值作为函数的结果返回.

为什么说 defun 是一个特殊形式呢? 因为按照上面解释, FUNCTION_NAME 是一个符号, 如果是正常的函数调用, 那么 Lisp 应该求这个符号的值的才对的, 但是这里没有, 就是把它当作符号来使用. 而 ARGUMENT_LIST 是一个列表, 按照 Lisp 的常规处理, 应该是把这个列表的值计算出来的, 但是这个列表全是符号, 第一个符号, 显然未必就是一个函数的, 按照正常计算的这个列表的值, 显然会出问题的. 因此, 我们知道 Emacs Lisp 在处理 defun 的时候, 和处理其他 S 表达式的时候, 是不一样的, 是特殊对待这个表达式的, 所以这个表达式叫做特殊形式, 也就是特殊的表达式.

写道这里, 回忆昨天学习的 setsetq , 显然 setq 也是一个特殊形式!

现在来了写一个实际的函数, 一个简单的把一个数翻倍的函数:

(defun double (number)
       "double function, return number is double as input number "
       (* number 2))
(double 3)

这样我们就定义了一个叫做 double 的函数了, 现在把光标放在这个函数的定义后, 按下 C-x C-e, 你会在回显区看到函数名 double 显示出来了. 如果使用 defun 定义一个函数没有语法的错误的话, defun 返回的就是这个函数的名. 那么怎么调用我们写好的这个函数呢? 像上面的代码中, 第二个列表那样来调用. 现在把光标移动到上面代表中的第二个列表末尾, 再次输入 C-x C-e, 可以看到在回显区域显示出了正确的计算结果 6. 如果我们在计算第一个列表的值之前就计算第二个列表的值, 很可能得到的结果提示错, 原因是我们还Emacs Lisp 找不到 double 定义. 一旦我们执行了定义 double 的表达式之后, double 就在Emacs Lisp 中落下了跟, 直到你的 Emacs 退出运行. 当下一次从新启动 Emacs 的时候, 就需要重新让 Emacs Lisp 执行定义 double 的列表, 这里我们叫作 Emacs Lisp 加载函数,或安装函数.

如果想让 Emacs 启动的时候, 就自动的执行我们的函数的定义, 有几种方法, 第一种是把函数的定义写在你的 .emacs 文件中. 第二种方法是把你的函数写在一个独立的文件中, 然后在你的 .emacs 文件中, 使用 load 来加载. 这些也就是我们加载一个 Emacs 插件做的工作. Emacs 插件, 说白了, 也就是 Emacs Lisp 函数的定义而已, 只是可能对这些函数做了键绑定.

在使用 Emacs 的时候, 我们还常常使用 M-x funcation-name 来调用函数. 但是现在我们写的函数, 只能通过计算 s 表达式的方法来调用. 那么怎么让我们的函数可以交互的被调用呢? 那就是我们上面将 OPTION_INTERACTION 部分的功能.

下面我们通过经典的 Hello World 的程序来看看交互的程序怎么定义. 首先我们还是按照原来的非交互的方法来定义 Hello-World 函数.

(defun Hello-World ()
       "Hello-World function, just print String \"Hello World\""
       (message "Hello World"))

在使用 C-x C-e 计算了 Hello-World 的定义之后, 我们输入 M-x Hel 并没有看到期望的 Hello-World 的补全, 这就是说 Emacs Lisp 找不到交互的 Hello-World 函数.

现在我们把 Hello-World 修改为交互的形式:

(defun Hello-World () ; interaction version
       "Hello-World function, just print String \"Hello World\""
       (interactive)
       (message "Hello World"))

现在把光标放在上面的代码上面, 输入 C-x C-e 重新计算 Hello-World 的定义, 然后再次输入 M-x Hel-, 现在你应该看到 Hello-World 的的补全了. 按回车, 你看到在回显区域, 显示出了 Hello World. 比较以上的两段代码, 唯一的差别, 就是交互性版本, 在第 3 行 多了 (interactive) 这个表达式. 我们知道, 这个表达式是调用函数 ineractive. 所以, 对函数 ineractive 的调用, 使得定义的函数, 可以交互式的调用了. 当然, 使用计算 S 表达式的方法还是可以调用的. 不信你就在 (Hello-World) 后面按下 C-x C-e, 看看是不是得到了和交互式调用一样的效果.

如果你仔细, 你会发现, 其实交互式调用和计算 S 表达式得到的结果是不一样的, 计算 S 表达式得到的结果, 是 "Hello World", 而交互式调用得的的结果是 Hello World. 这是因为, 对于交互式调用来说, 函数调用的副作用, 就是把字符串显示出来, 对人来说, 是有用的信息, 人关心的是信息, 而不是返回的值的类型, 所以这里没有引号. 而计算 S 表达式, 意味着, 你告诉 Emacs Lisp, 你关心的不是副作用, 所以 Emacs Lisp 就原原本本的把副作用显示出来.

上面的例子是对 interactive 函数, 最简单的用法. 现在看几个稍微高级点的例子. 在学习其他编程语言的时候, 我们都有输入输出的的问题, 那么在 Emacs Lisp 中如何输入呢? 这也是 interactive 函数来完成的. 例如下面的函数定义, 可以交互的输入一个数字, 然后把显示出来:

(defun Show (number)
      "Show function, show the number"
      (interactive "nPlease input a number:")
      (message "number is %s" number))

Show 函数加载到Emacs Lisp 之后, 我们使用 M-x Show 就能看到提示信息, 然后展示出来结果.

你大概能猜到, 所以会有提示信息出来, 一定是因为 (interactive "nPlease input a number:") 的原因, 因为提示信息和参数字符串非常的相识. 但是有一点点的不同, Please 前面的 n 没有在提示信息中出现, 那么这个 n 在这里是什么东西呢? 它是用来表示交互输入的内容要转化成一个数字(number), 然后数字赋值给 number. 现在我们知道了, 如何交互的输入一个参数了, 那么如何交互调用有两个参数的函数呢? 看下来的代码:

(defun add (n1 n2)
       "add n1  n2 show result"
       (interactive "nPlease input a number for n1:\nnPlease input a number for n2:")
       (message "%s + %s = %s" n1 n2 (+ n1 n2)))

加载 add 函数进入 Emacs 中, 输入~M-x add~ 然后按回车, 你首先看到提示输入 n1 的值, 输入正确之后, 你看到提示输入 n2 的值, 再次输入后, 你会看到结果.

仔细看上面代码对 interactive 的调用, 你也许能猜到秘诀可能在 \n 上. 的确 interactive 就是用 \n 来区分不同的代码字母的. 代码字母, 就是和上面的 Please 前面的 n 一类的字母, 这些字母告诉 Emacs Lisp 把用户的输入转化成特定的值, 然后赋值给对应为此的参数. 更多的内容查看 Emacs 帮助文档.

你可能感兴趣的:(Emacs Lisp 学习(2))