An Introduction to Programming in Emacs Lisp

这本书 emacs 里有,按“C-h i”,输入“m,Emacs Lisp Intro”就能看到。我把其中比较基础的部分挑出来翻译了一下。

Lisp 语言的历史

Lisp 语言最早是在 20 世纪 50 年代末由麻省理工学院(MIT)为研究人工智能而开发的。Lisp 语言的强大使它在其它方面诸如编写编辑命令和集成环境等显示其优势。而 GNU Emacs Lisp 主要由 Maclisp 发展而来,该语言由 MIT 在 20 世纪 60 年代写成。它在某种程度上继承了 Common Lisp,而 Common Lisp 在 20 世纪 80 年代成了一种标准。但是 Emacs Lisp 要比 Common Lisp 简单得多。

表处理

Lisp 代表 LISt Processing,即表处理,这种编程语言用来处理由括号(即“(”和“)”)构成的列表。括号标记着表的边界,有时候一个列表之前加了一个撇号(即“'”,或者说是单引号)。列表是 Lisp 语言的基础。在 Lisp 语言中,一个列表看起来像这样:“'(rose violet daisy buttercup)”。也可以写成以下这种形式:

'(rose
violet
daisy
buttercup)

这样看起来更为熟悉。这个表的元素是四种花的名字:rose,violet,daisy 和 buttercup。列表中也可以包含数字,如“'(1 2 3)”这种形式。在 Lisp 语言中,数据和程序用同一种方式表示,就是说它们都是由单词、数学或者其它表组成的,中间用空格分开,两边加上括号。由于一个程序看起来像数据,它可以很容易地在其它地方用作数据。这是 Lisp 语言一个非常强大的特性。括号内带有分号和句号(即“;”和“.”)的句子不是列表。这里列举了另外一种形式的列表:

'(this list has (a list inside of it))

该表的元素包括 this,list,has 和一个子表 (a list inside of it)。这个子表由元素 a,list,inside,of,it 组成。

原子

在 Lisp 语言中,我们将单词叫做“原子”,因为原子是不可分的。一个列表可以由一个或多个原子组成,或者一个都没有,不包含任何原子的列表形如“()”,也可以写作“nil”,被称为“空表”。空表既可看作是一个原子,也可以看作是一个列表。原子和列表都被称为“符号表达式”。另外,由双引号标记的文本(甚至可以是句子或者段落)也是一个原子,如:

'(this list includes "text between quotation marks.")

这种形式的原子,即 "text between quotation marks." 也叫字符串。

执行程序

Lisp 语言中的一个表就是一个可执行的程序。如果你运行它(用 Lisp 术语来说就是“求值”),计算机会按如下三种情况中的一种执行:一是什么也不做,仅仅返回列表本身,二是返回错误信息,三是将列表中的第一个符号作为一个命令进行一些操作。如果在一个列表前加上“'”,当计算机处理该列表时就执行第一种情况,如果列表前没有“'”,则表中第一项具有特殊意义,它是一种命令(在 Lisp 语言中,这些命令被称为函数)。比如:

(+ 2 3)

这个列表执行(用术语来说就是“对列表求值”)的结果是 5,因为该表的第一项是“+”,表示对表中剩下的元素进行求和。如果在 Emacs 中执行,只需要将光标移动到右括号后方,然后执行“C-x C-e”即可,在回显区可以得到结果“5”。这样的方式是将命令递交给程序(在 Emacs 中被称为“Lisp 解释器”)的过程。

对子表求值

如果对一个包含另外一个子表的列表进行求值,外部列表可以利用内部子表返回的值进行求值。这也解释了为什么内部表达式先被求值,因为这些返回值需要被外部表达式利用。如:

(+ 2 (+ 3 4))

执行上一列表可在回显区得到结果 9,因为 Lisp 解释器先在对内部表达式“(+ 3 4)”进行求值,得到 7,再对外部表达式“(+ 2 7)”进行求值得到 9。

变量

在 Emacs Lisp 中,一个符号可以指代一个值,也可以指代一个函数定义。两者是有区别的,函数定义是一套计算机需要执行的指令,而一个值则是不同于其它事物的东西,比如一个数或者一个名字。一个符号的值可以是任何 Lisp 表达式,通常也被称为“变量”。一个符号还可以同时指代一个值和一个函数定义。

参数

参数是指需要提交给函数的信息。不同的函数需要不同的参数个数,一些函数甚至不需要参数。例如:

(concat "abc" "def")

列表中“concat”函数将两个或多个字符串连接起来形成一个字符串,此函数所带的参数就是字符串,对该列表求值得到结果“abcdef”。一些函数如“concat”,“+”或者“*”可以有任意个数的参数,例如没有参数(即 0 个参数):

(+)
(*)

对以上两个表达式求值,分别得到 0 和 1。与此类似的还有“message”函数,它用来给用户发送消息,如:

(message "My name is %s and I am %d years old." "Xiaodong Xu" 25)

对上表求值得到“My name is Xiaodong Xu and I am 25 years old.”。其中双引号“"”中的“%s”并不直接显示,而是搜寻该字符串后面的参数,它对第二个参数“Xiaodong Xu”求值并将它的值显示在“%s”所在的位置。同理,“%d”用第三个整数参数 25 来代替。

为变量赋值

有好几种方法可以给变量赋值。一种是用“set”或者“setq”函数,另一种是用“let”函数。这种过程用术数来说就是给一个变量“绑定”一个值。用“set”函数赋值可用如下形式:

(set 'flowers '(rose violet daisy buttercup))

对上表求值时,列表“(rose violet daisy buttercup)”将在回显区显示,这是“set”函数返回的值。作为一种附带效应,符号“flowers”被绑定到这个列表,也就是说“flowers”这个变量被这个列表所赋值。另外,这种过程也说明了 Lisp 解释器的一种附带效应,即赋值,可以是我们人所期望的主效应。在实际应用中,几乎每个赋值语句的第一个参数都需要加上撇号“'”,由于这种情况很常见,set 和第一个参数前的“'”组合起来构成了一个特定的形式“setq”,如:

(setq carnivores '(lion tiger leopard))

这跟

(set 'carnivores '(lion tiger leopard))

完全等价。

“setq”也可用来把不同的值赋给不同的变量,第一个参数被赋给第二个参数的值,第三个参数被赋给第四个参数的值,以此类推,比如,要把一组“trees”赋给“trees”符号,同时把一组“herbivores”赋给“herbivores”符号,可以这样做:

(setq trees '(pine fir oak maple)
herbivores '(gazelle antelope zebra))

这里用了“赋值”这个词语,另一种思维方式是“setq”把这个符号“指向”了这个列表。后者非常通用。

函数

除了一些用 C 语言写的“基本”函数,所有函数都是根据其它函数来定义的。定义函数时,可以在函数体中使用其它的函数,有些是用 Emacs Lisp 写的,有些是用 C 写的基本函数。用 C 写的目的是能够使 GNU Emacs 更容易在任何有 C 语言条件的计算机上运行。但是不必区分用 C 写的函数和用 Emacs Lisp 写的函数在使用上的区别。一个函数定义至多由五个部分组成: 1. 函数符号的名称。 2. 需要传递给函数的参数列表。如果没有参数,就是一个空表“()”。 3. 用于描述函数的文档(技术上可选,但是强烈建议写上)。 4. 可以使函数与人交互(即可以用“M-x”加上函数名称,或者用合适的键或键组合来调用函数)的表达式,可选。 5. 要求计算机执行的代码,即函数体。

函数定义可以写成如下模板:

(defun FUNCTION-NAME (ARGUMENTS...)
"OPTIONAL-DOCUMENTATION..."
(interactive ARGUMENT-PASSING-INFO)     ; optional
BODY...)

举个例子,定义一个乘以7的函数:

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

对此表求值后,我们就将“multiply-by-seven”这个函数装入 Emacs 了。此时再执行如下语句:

(multiply-by-seven 3)

在回显区可以得到结果 21。

“let”特殊形式

“let”表达式是 Lisp 语言的一种特殊形式,在大多数函数定义中都会被用到。“let”用于防止混淆,它创建一个“局部变量”的名字,这个名字将覆盖“let”表达式之外所有的同名变量。由“let”表达式创建的局部变量仅在“let”表达式内部保留,对外部没有影响。“let”一次可创建多个变量,并给每个变量创建一个初始值,初始值可以是某个特定的值,也可以是空(“nil”)。在“let”创建变量之后,将执行“let”主体中的代码并返回最后一个表达式的值。“let”表达式是由三部分组成的列表,第一部分是符号“let”,第二部分是一个列表,称为“变量列表”,每个元素可以是一个符号或者是一个二元列表(第一个元素是符号),第三部分是“let”表达式主体。可以用以下的模板来说明“let”表达式:

(let VARLIST BODY...)

如果变量列表由二元组列表组成(这种情况比较常见),“let”表达式的模板可以写成这样:

(let ((VARIABLE VALUE)
(VARIABLE VALUE)
...)
BODY...)

下面给出创建并初始化两个变量“zebra”和“tiger”的表达式,“let”表达式主体是一个调用“message”函数的列表。

(let ((zebra 'stripes)
(tiger 'fierce))
(message "One kind of animal has %s and another is %s."
zebra tiger))

“if”特殊形式

除“defun”和“let”之外,还有条件“if”。这种形式用于让计算机做判断。“if”背后的基本涵义是,“如果一个判断为真,那么一个表达式将被求值”。如果判断为假,表达式不会被求值。判断和执行部分是“if”列表的第二和第三部分,而第一个元素是“if”。不过,“if”表达式的判断部分常被称为“if部分”,而执行部分常被称为“then部分”。 “if”表达式也可以包含第三个可选参数,即“else部分”,用于判断为假的情况。在这种情况下,then 部分不会被求值,而 else 部分将被求值。这时“if”表达式可写成如下模板:

(if TRUE-OR-FALSE-TEST
ACTION-TO-CARRY-OUT-IF-THE-TEST-RETURNS-TRUE
ACTION-TO-CARRY-OUT-IF-THE-TEST-RETURNS-FALSE)

举个例子,

(if (> 4 5)                             ; if-part
(message "5 is greater than 4!")    ; then-part
(message "4 is not greater than 5!")) ; else-part

判断为假时,表达返回“nil”。值得注意的是,“nil”在 Emacs Lisp 中有两种含义,一种代表空表,第二种代表假,而任何非空的值都被认为是真。

基础函数

在 Lisp 语言中,“car”,“cdr”和“cons”是基础函数。“cons”函数用于构建列表,“car”和“cdr”函数用于拆分列表。一个表的 CAR 就是该表的第一项,如:

(car '(rose violet daisy buttercup))

执行该表达式可得到“rose”。同理,一个表的 CDR 是该表的剩余部分,就是说,“cdr”函数返回该表第一项后面的部分。如

(cdr '(rose violet daisy buttercup))

执行得到“(violet daisy buttercup)”。同“car”一样,“cdr”既不会修改也不会从列表中删除元素,而仅仅是返回一个值。这是一个非常重要的特性。 “cons”函数与“car”和“cdr”相反,比如“cons”可以从一个三元表组成一个四元表:

(cons 'pine '(fir oak maple))

执行后在回显区得到结果“(pine fir oak maple)”。“cons”生成一个新列表,此列表中元素“pine”后面跟有原始列表中的元素“(fir oak maple)”。可以说“cons”将一个新元素放在一个表的开头,或者说是将其压在一个表上方,从而生成一个新的列表,而不改变原来列表的值。 “length”函数可以得到一个表中元素的个数,如:

(length (cons 'violet '(daisy buttercup)))

由于(cons 'violet '(daisy buttercup))返回一个三元列表,所以上表执行得到结果 3。空表的长度为 0。 “nthcdr”函数与“cdr”函数相关,它所做的是对一个列表重复执行“cdr”多次。如:

(nthcdr 2 '(pine fir oak maple))

得到结果“(oak maple)”。 “nth”函数返回一个列表的第 N 个元素,以 0 为起点,如:

(nth 1 '("one" "two" "three"))

得到结果“two”。 “setcar”和“setcdr”函数与“car”和“cdr”函数类似,但是它们会改变原有列表的值。

列表的实现

在 Lisp 语言中,原子用直接的方式记录,如果实际上不直接,在理论上也是直接的。对一个表就另当别论了,它是由一个指针对序列构成的。在这个序列中,每个指针对的第一个指针指向一个原子或者另一个列表,第二个指针指向另一个指针对,或者指向空符号(即“nil”,用于标记列表的结束)。一个指针本身是它指向的内容的地址,所以一个表就是一个地址的序列。比如列表“(rose violet buttercup)”的实现可以用下图简单表示:

    ___ ___      ___ ___      ___ ___
|___|___|--> |___|___|--> |___|___|--> nil
|            |            |
|            |            |
--> rose     --> violet   --> buttercup

当一个变量被诸如“setq”之类的函数赋给一个列表时,它存储的是第一个长方形的地址。所以对表达式

     (setq bouquet '(rose violet buttercup))

求值的结果可以这样表示:

bouquet
|
|     ___ ___      ___ ___      ___ ___
--> |___|___|--> |___|___|--> |___|___|--> nil
|            |            |
|            |            |
--> rose     --> violet   --> buttercup

综合之前的概念,一个符号可以看作一个抽屉柜,用下图表示:

            Chest of Drawers            Contents of Drawers
__   o0O0o   __
/                 /
---------------------
|    directions to    |            [map to]
|     symbol name     |             bouquet
|                     |
+---------------------+
|    directions to    |
|  symbol definition  |             [none]
|                     |
+---------------------+
|    directions to    |            [map to]
|    variable value   |             (rose violet buttercup)
|                     |
+---------------------+
|    directions to    |
|    property list    |             [not described here]
|                     |
+---------------------+
|/                   /|

循环和递归

Emacs Lisp 有两种主要的方式来重复求值:一种用“while”循环,另一种用“递归”。 “while”表达式主要有以下几种形式:

简单形式

(while TRUE-OR-FALSE-TEST
BODY...)

空表判断循环

(while TEST-WHETHER-LIST-IS-EMPTY
BODY...
SET-LIST-TO-CDR-OF-LIST)

增量计数循环

SET-COUNT-TO-INITIAL-VALUE
(while (< count desired-number)         ; true-or-false-test
BODY...
(setq count (1+ count)))              ; incrementer

减量计数循环

SET-COUNT-TO-INITIAL-VALUE
(while (> counter 0)                    ; true-or-false-test
BODY...
(setq counter (1- counter)))          ; decrementer

递归函数包含了 Lisp 解释器用来调用自身函数的代码,但是这些函数的参数有着细微的差别。用术语说就是有着不同的“实体”。

一个简单的递归函数形如:

(defun NAME-OF-RECURSIVE-FUNCTION (ARGUMENT-LIST)
"DOCUMENTATION..."
(if DO-AGAIN-TEST
BODY...
(NAME-OF-RECURSIVE-FUNCTION
NEXT-STEP-EXEXAMPLESSION)))

另一种形式是用“cond”函数:

(cond
(FIRST-TRUE-OR-FALSE-TEST FIRST-CONSEQUENT)
(SECOND-TRUE-OR-FALSE-TEST SECOND-CONSEQUENT)
(THIRD-TRUE-OR-FALSE-TEST THIRD-CONSEQUENT)
...)

如果第一个判断条件返回空(即为假),第一个判断语句将被跳过,然后再对第二个判断条件求值,以此类推。如果某个判断条件返回真,该判断语句将被求值。


Emacs Lisp语言相关推荐

  1. 你不可不知的9种Lisp语言思想

    本文来源 Lisp语言诞生的时候就包含了9种新思想.其中一些我们今天已经习以为常,另一些则刚刚在其他高级语言中出现,至今还有2种是Lisp独有的.按照被大众接受的程度,这9种思想依次如下排列. (1) ...

  2. Common Lisp语言快速入门

    zhezhelin Common Lisp语言快速入门 Lisp是软件领域的分裂力量.一方面,Lisp爱好者誓言Lisp比软件领域内的其它语言都更加快捷.整洁和强大:而反对者则辩称,不可靠的执行和库支 ...

  3. lisp语言是最好的语言_Lisp可能不是数据科学的最佳语言,但是我们仍然可以从中学到什么呢?...

    lisp语言是最好的语言 This article is in response to Emmet Boudreau's article 'Should We be Using Lisp for Da ...

  4. Emacs Lisp基本语法(六)

    简介 Lisp,名称源自列表处理器(英语:List Processor)的缩写,最早由约翰·麦卡锡在1958年基于λ演算创造,演化至今,是历史第二悠久的高级语言,仅次于Fortran,也是第一个函数式 ...

  5. lisp语言1y1c,常青树LISP语言

    常青树LISP语言 介绍 Lisp是历史最悠久的编程语言之一,接近五十年.Lisp以一种简洁的方式有效地实现了多种高级语言设计的目的.LISP全名叫LISt Processor,List是LISP的主 ...

  6. (转)为什么LISP语言如此先进?

    很高兴今天又读到了一篇非常有启发性的文章.说实话,读完后虽然有些部分还是觉得有点模糊,但这种模糊里还是有一点感觉.哈哈,先不说这些玄乎的话了.在我看来,是应该学习LISP的,感觉这门语言被推向了神坛, ...

  7. 为什么Lisp语言如此先进?(译文)

    上周,<黑客与画家>总算翻译完成,已经交给出版社了. 翻译完这本书,累得像生了一场大病.把书稿交出去的时候,心里空荡荡的,也不知道自己得到了什么,失去了什么. 希望这个中译本和我的努力,能 ...

  8. lisp语言(转自百度)

    lisp语言 Lisp 语言最早是在 20 世纪 50 年代末由麻省理工学院(MIT)为研究人工智能而开发的.Lisp 语言的强大使它在其它方面诸如编写编辑命令和集成环境等显示其优势.而 GNU Em ...

  9. [转]为什么Lisp语言如此先进?

    为什么Lisp语言如此先进? 作者: 阮一峰 日期: 2010年10月14日 上周,<黑客与画家>总算翻译完成,已经交给出版社了. 翻译完这本书,累得像生了一场大病.把书稿交出去的时候,心 ...

最新文章

  1. python学习手册笔记——20.迭代和解析
  2. WCF+Nhibernate 序列化的问题。
  3. 1349. 修理牛棚【难度: 中 / 思维 贪心】
  4. String对象的intern()
  5. 十步让你成为一名优秀的 Web开发人员
  6. workflow initialization in webclient ui - Remote call case
  7. 便利蜂发布《白领早餐报告》:仅5成白领每天吃早餐
  8. 对有些反编译不成功的apk,请更新最新的apktool.jar、 dex2jar试试
  9. 图像处理-与,或等运算
  10. calendar与date区别及常用方法介绍
  11. Spring源码的学习方法和知识地图
  12. golang copy-on-write思想应用
  13. 2020年日历电子版(打印版)_2020年日历表(高清A4可打印版).pdf
  14. 最简单的Tomcat9下载安装教程
  15. Flink流处理框架下的交通灯控制器
  16. 汉诺塔怎么加计数次数c语言,C语言算汉诺塔,递归时的输出是怎么一步一步来的?如图,求大神帮忙...
  17. L1-054 福到了 (15 分)C语言
  18. remove logo now注册码
  19. vs2008使用技巧推荐
  20. Android监听手机软键盘的弹起和关闭

热门文章

  1. 【VMware Fusion】如何配置VMware Fusion中的Vmnet网卡
  2. 饥荒机器人升级上限多少_饥荒机器人怎么升级
  3. isbn书号权威查询,isbn图书查询,图书
  4. 研发计算机主板需要什么专业,一种计算机主板研发用固定装置的制作方法
  5. 5种阿里常用代码检测推荐 | 阿里巴巴DevOps实践指南
  6. 基于OTSU最大类间方差法的ROI分割、提取图像中的形状特征--面积、周长、离心率、zernike矩
  7. 天气学诊断实习四 计算垂直速度
  8. 前端技术盘点以及 2016 年技术发展方向
  9. error LNK2005: _DllMain@12 已经在 MSVCRTD.lib(dllmain.obj) 中定义
  10. 微信小程序、微信小游戏作品汇总合集,各种好玩的小程序