Emacs Lisp 学习(1)

作者: 桂叶圣 | 来源:发表于2016-10-09 23:38 被阅读495次

    这是一个读书笔记, 是我学习 Emacs Lisp 的笔记. 这是这个笔记的第一篇.

    使用 Emacs 做编辑器的人, 大部分是程序员, 学习 Lisp 的人, 很多也是因为 Emacs 中嵌入的的 Lisp 而开始留心 Lisp 的. Emacs Lisp 是 Lisp 语言的一个方言, 它嵌入在 Emacs 编辑器中. 要为 Emacs 编辑器写特定的扩展插件, 就使用 Emacs Lisp 写程序. 所以学习 Emacs Lisp 的一个天然的动机, 就是理解, 修改或者自己编写 Emacs 的插件. 当然, Emacs Lisp 也可以作为一个编程入门来学习, 虽然我觉的这个入门可能有点高.

    在学习Emacs Lisp 之前, 首先要求你有一定的 Emacs 的使用经验. 基础的 Emacs 操作, 不是我这个笔记的内容. 这里主要关注的是如何用 Emacs Lisp 编程. 要编程, 就需要有一定的环境, 正如学习 C/C++ 你需要有 C/C++ 编译器, 学习 Emacs Lisp, 要求你有 Emacs. 我这个文档是在 Emacs 中写的, 也假设, 你做的所有的编程的动作也都是在 Emacs 中的.

    这篇文章, 我们讲 Emacs Lisp 的基本等概念. 如果没有可以指出是 Emacs Lisp , 那解释的就所有 Lisp 的内容, 否则就是 Emacs Lisp 特有的特性.

    Lisp 是什么?<a id="sec-1-1" name="sec-1-1"></a>

    不熟悉 Lisp 的人看到 Lisp 代码会觉得 Lisp 是一个古怪的编程语言. 在 Lisp 中, 几乎到处都是小括号, 因此有人说 Lisp 是 "Lots of Isolated Silly Parentheses" (很多独立的傻括号), 这当然是玩笑, Lisp 是 "LISt Processing"(列表处理) 的缩写. 顾名思义, 这个语言就是处理 列表 的. 那么我们就看看在 Lisp 中是到底什么是列表.

    Lisp 中的列表<a id="sec-1-2" name="sec-1-2"></a>

    很多人用软件做时间管理, 给自己制定代办事项, 待办事项就是一个典型的列表. 比如我们用 HTML 中的 Lisp 来表示一个待办事项那就是这样的:

    • [ ] 第一件事
    • [ ] 第二件事
    • [ ] …
    • [ ] 第 N 件事

    如果我们自己动手做一个代办事项的话, 很可能是这样的:

    第一件事
    第二件事
    ......
    第 N 事
    

    当然细心的人, 可能也会自己在前面留出空白, 甚至是画上在其中做标记的方格, 但是大部分人, 大概像我这样. 我所以特意的把可能会有的手工的待办事项列举出来, 是因为, 手工的代表事项列表, 非常的接近 Lisp 中的列表, 接近到什么程度呢? 我们只需要在手工代办实现列表的最前面, 和最后面加上括号就是了. 下面是 Lisp 中用列表表示代办事项列表:

    (第一件事
    第二件事
    第三件事)
    

    Lisp 中的列表, 是由被小括号括起来的, 有空白分隔列表元素组成的. 现在我们需要讨论列表的元素是什么了. 列表的元素可以是原子或者是另外一个列表. 原子是什么呢? 在 Lisp 中所原子, 其实使用的是原子的历史含义, 历史上认为自然是由不可分的原子组成的, 所以原子的意思是 "不可分割". 在 Lisp 中使用的就是这个意思. 如果一个东西是不少分割的, 在 Lisp 中就是原子. 所以, 数字, 符号, 字符串, 都是原子. 下面的例子给出的都是 Lisp 列表:

    (1 2 3 4 5)        ;全部有数字构成的列表
    (a b c d d)        ;全部有符号构成的列表
    ("a" "b" "c" "d")  ;全部有字符串构成的列表
    (1 a "b" ("c d"))  ;混合列表
    

    第一个列表全部有数字构成; 第二个列表全部由符号构成, 符号就是所有的字符连接起来的没有被空白分割的东西; 第三个列表全部是有字符串构成; 第四个需要仔细了, 首先要注意的是, 它的元素只有 4 个而不想前三个那样是 5 个. 第一个是一个数字, 第二是一个符号, 第三个是一个字符串, 第四个本身也是一个列表, 这个列表包含了 一个 字符串 ~"c d"~.

    Lisp 的列表对空白的要求很松, 只要有一个空白就可以, 多个空白也接收, 用空格也可以, 用 <Tab> 也可以, 用换行也没问题. 对 Lisp 来说, 这些都我关紧要. 所以上面的待办事项 Lisp 列表的例子和下面的代码在 Lisp 看来是一样的:

    (第一件事 第二件事 第三件事)
    

    所以, 为了可读性, 你愿意使用什么样的空白来分隔 Lisp 的列表元素都可以. 还有一点, 如果列表的元素也是一个列表, 那么这个子列表和其他元素自己可以没有空白. 例如一下例子中的几个列表是等价的:

    (a(b)c)
    (a (b) c)
    (a ( b ) c)
    

    总结一下, 在 Lisp中, 列表就是用括号把元素括起来; 数字就像我们平常用的那样; 字符串是用双信号括起来的字符内容.

    现在大家对符号可能有点疑惑, 符号的定义是什么? 可以简单的把符号理解为其他编程语言中的标识符. 但是 Lisp 中的符号比其他语言中的标识符还宽松, 以数字大头也是合法的. 比如一下都是Lisp的合法的符号:

    a
    a1
    1a
    

    但是数字不是合法的符号. 我在书中也没有看到关于符号的精确的定义, 但是我知道数字不是符号, 后面会看到我是怎么知道这些的.

    如何运行 Emacs Lisp 程序<a id="sec-1-3" name="sec-1-3"></a>

    首先我们要清楚对于 Lisp 语言来说到底什么是程序? 对 Lisp 来说, 程序就是列表, Lisp 处理这个列表, 就是执行这个程序, 所以 写 Lisp 程序就是要写列表. 那么怎么让 Lisp 处理我们写的列表呢? 在 Emacs 中有专门的键盘绑定 C-x C-e. 现在先不要着急马上在你的 Emacs 中按下那个快捷键. 跟着我的介绍, 我们慢慢来做, 首先在你的 Emacs 中输入一下 Lisp 的列表:

    (+ 2 3)

    那么现在你的光标应在在闭括号的后面, 不要移动它. 现在按 C-x C-e. 不要动光标, 注意你的回显区域. 如果一切正常的话, 回显区会显示出来的数字 5.

    如果你有一定的 Emacs 的使用经验, 你会知道所有的 Emacs 的快捷键都对应这 Emacs Lisp 代码, 而且应该知道按下 C-h k , 回车然后再按下对应的快捷键, 就能看到这个快捷对应的 Emacs Lisp 函数说明. C-x C-e 对应的是 eval-last-sexp 这个函数的调用. 这个函数把光标前的 s 表达式 传给 Emacs Lisp 程序, 让 Emacs Lisp 来处理这个表达. 注意这里提到的是 s 表达式 而不是列表, 因为除了列表外, Lisp 还能处理原子. 可以把 s 表达式理解为原子和列表. 当然我这里说的不是全部的内容, 因为 s 表达式, 这一说法, 如果要解释清楚, 就需要回到 Lisp 发明之初的历史. 有兴趣的读者, 可以自己查询一下 s 表达式的文献.

    在最后一个例子中, 我们看到 Emacs Lisp 给出了 (+ 2 3) 的结果是 5, 不难猜到, 这是做了数字 2 与 3 的加法运算. 那么 Lisp 是怎么计算的呢? Lisp 到底是怎么处理 s 表达式呢?

    Lisp 工作原理<a id="sec-1-4" name="sec-1-4"></a>

    Lisp 对 s 表达式的处理逻辑,非常的简单.

    1. 如果 s 表达式是一个原子, 那么获得这个原子的值, 作为返回结果.
    2. 如果 s 是一个列表, 那么 Lisp 首先检查 列表的第一个元素是不是一个函数
      1. 如果列表的第一个元素是函数, 那么 Lisp 获得这个函数的指令, 然后用列表后面的内容作为参数来调用这个函数.
      2. 如果列表的第一个元素不是函数, 那么 Lisp 给出错误信息, 结束执行.
      3. 如果列表中的元素有列表, 那么首先计算内部列表的值, 返回结果作为内部列表的值, 返回到外部列表中, 最后计算外部列表.

    我们来看一个例子:

    (+ 2 3 (* 4 5))
    (+ 2 3 20)
    

    如果把光标放在第一个列表数字 5 后面的括号后, 按 C-x C-e ,可以计算出来 (* 4 5) 的值是 20. 计算这两个列表的值, 可以得到的结果都是 25.

    更多的时候, 我们写程序不想让 Lisp 计算结果, 只是就是想写一个列表, 这个时候可以这样来写:

    '(a b c)
    (a b c)
    

    我们把光标移动到例子中第一个列表的后面, 然后按 C-x C-e 会发现, 结果给出的就是列表本身. '(a b c) 实际上是 (quote (a b c)) 的简写. 所以在 Lisp 中单引号有叫做 '引用 (quote)'.

    如果我们计算第二个列表的值, 我们会发现错误了. Emacs Lisp 中给出的提示内容是这样的:

    eval: Symbol's function definition is void: a
    

    这是说计算 (eval) 的时候, 发现符号(这里指的是 a )的函数定义是空的. 因为列表的第一个元素是符号 a, 因此 Emacs Lisp 认为这个符号的值是一个函数定义的位置, 但是因为实际上, 我们还没有定义这个符号, 所以这个符号的内容是空的, 所以 Emacs Lisp 就认为这个函数的定义是空的. 如果我们把光标放在 a(任何位置的, 可以不在列表中) 的后面, 按 C-x C-e , 我们会得到如下的提示内容:

    eval-last-sexp-1: Symbol's value as variable is void: a
    

    这次意义更清楚, 做为变量符号(a)的值是空的.

    通过这两个例子, 我们可以看到 Emacs Lisp 的确是安装我们描述的 Lisp 的工作原理那样来工作的.

    变量赋值<a id="sec-1-5" name="sec-1-5"></a>

    有集中方法对变量复制, 今天介绍 setsetq.
    setsetq 对变量赋值的语法如下:

    (set 'a "Sting a")
    (setq b "String b")
    

    这里特别需要注意的是 setsetq 的差别, set 的第一个参数, 我们是引用 a, 而 setq 的第一个参数, 我们直接使用的就是符号 b. 'a 的计算结果是符号 a, 而 a 的计算结果是什么, 就不好说了, 这要看符号 a 的值是什么了.

    可能现在大家对 set 的用法还不好理解, 但是我想对 setq 的用法应该不难理解, 就是给符号赋值. 下面的代码或许能帮助理解 set.

    (setq a 'b)
    (set a "This is Symbole b")
    a
    b
    

    如果我们依次计算上面代码中的两个别表, 那么现在计算 a 的值应该是多少呢? 答案是符号 b. b 的值是 "This is Symbole b".

    如果熟悉 C/C++ 的话, 那么我们可以把 set 的行为理解为是设置指针的指向内容的值. 而把 setq 理解为设置符号的值.

    现在我们再来看看 setq 如果第一个参数不是一个符号会怎么样呢?

    (setq 'a "Error")
    (setq a 'd)
    (setq a "This is Symbole d")
    

    你看到了, 执行第一行, 你会看到 Emacs Lisp 提示参数类型不对了. setq 的第一个参数必须是符号. 而 set 却不是这样的. 执行第二行和第三行, 现在 a 的内容是"This is Symbole d", 而不是符号 d.

    setq 还有别的功能, 就是可以一次给多个符号赋值, 例如下面的例子给符号 a 复制为 1, b 复制为一个列表:

    (setq a 1 b '(1 2 3 4))
    a
    b
    

    我们前面说过, Lisp 中符号, 类似于其他编程语言中的标识符, 但是比标识符更加的宽松, 你甚至可以以数字开头, 而且我说过虽然我现在还没有看到 Emacs Lisp 中对符号的定义, 但是我能知道, 以数字大头定义的标识符在 Lisp 中是合法的. 所以这样就是因为, setq. 例如下面的代码, 在 Emacs Lisp 中执行是没有错的.

    (setq 1a "Symbole begin with digita is OK")
    1a
    

    在 Emacs 中依次计算 上面的两行代码, 可以得到 1a 的值是 "Symbole begin with digita is OK" .

    相关文章

      网友评论

        本文标题: Emacs Lisp 学习(1)

        本文链接:https://www.haomeiwen.com/subject/rjymyttx.html