Add New Notes

This commit is contained in:
geekard
2012-08-08 14:26:04 +08:00
commit 5ef7c20052
2374 changed files with 276187 additions and 0 deletions

View File

@@ -0,0 +1,363 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-01T14:03:56+08:00
====== Emacs Lisp语言 ======
Created Wednesday 01 February 2012
http://www.neu.edu.cn/cxsj/online/C1/Lisp%E8%AF%AD%E8%A8%80.htm
An Introduction to Programming in Emacs Lisp
这本书 emacs 里有按“C-h i”输入“mEmacs 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)
这样看起来更为熟悉。这个表的元素是四种花的名字rosevioletdaisy 和 buttercup。列表中也可以包含__数字__如“'(1 2 3)”这种形式。在 Lisp 语言中,**数据和程序用同一种方式表示**就是说它们都是由__符号(symbol)、数字或者其它表__组成的中间用空格分开两边加上括号。由于一个程序看起来像数据它可以很容易地在其它地方用作数据。这是 Lisp 语言一个非常强大的特性。括号内带有分号和句号(即“;”和“.”)的句子不是列表。这里列举了另外一种形式的列表:
'(this list has (a list inside of it))
该表的元素包括 thislisthas 和一个子表 (a list inside of it)。这个子表由元素 alistinsideofit 组成。
===== 原子 =====
在 Lisp 语言中,我们**将单词(word)叫做“原子”(atom)**因为原子是不可分的。__一个列表可以由一个或多个原子组成或者一个都没有__不包含任何原子的列表形如“()”也可以写作__“nil”__被称为“空表”。空表既可看作是一个原子也可以看作是一个列表。
__原子和列表都被称为“符号表达式” symbolic expressions__ or, more concisely, __s-expressions__。另外由双引号标记的文本甚至可以是句子或者段落也是一个原子
'(this list includes "text between quotation marks.")
这种形式的原子,即 "text between quotation marks." 也叫__字符串(string)__。
另外__以单引号开头的list也是原子__。
常见的atom包括number, string, symbol如+、-、*、/、flower、name等**与函数或变量绑定的单词**, quoted list.
__Lisp programming is mostly about symbols__ (and sometimes numbers) within lists.
__a list is between parentheses, a string is between quotation marks, a symbol looks like a word, and a number looks like a number.__
===== 执行程序 =====
Lisp 语言中的__一个表就是一个可执行的程序__。如果你运行它用 Lisp 术语来说就是“求值”),计算机会按如下三种情况中的一种执行:
* 一是什么也不做仅仅__返回列表本身__
* 二是返回错误信息
* 三是将列表中的第一个符号作为一个命令进行一些操作。
如果在一个列表前加上“'”,当计算机处理该列表时就执行第一种情况,如果列表前没有“'”,则表中第一项具有特殊意义,它是一种命令(在 Lisp 语言中这些命令被称为__函数__。比如
(+ 2 3)
这个列表执行(用术语来说就是“对列表求值”)的结果是 5因为该表的第一项是“+”,表示对表中剩下的元素进行求和。如果在 Emacs 中执行只需要将光标移动到右括号后方然后执行“C-x C-e”即可在回显区可以得到结果“5”。这样的方式是将命令递交给程序在 Emacs 中被称为“Lisp 解释器”)的过程。
__evaluating a symbolic expression(原子或列表) most commonly causes the Lisp interpreter to return a value and perhaps carry out a side effect; or else produce an error.__
__将单引号放在list或symbol前面表示不对这个list或symbol求值而是返回紧跟引号的 printed representation。__
You can also__ evaluate an atom that is not part of a list__—one that is not surrounded by parentheses;
__可以直接执行atom__如number, stringsymbol (**代表函数名的symbol不能直接执行**必须要放在list中执行。)。
In the expressions, the parentheses tell the Lisp interpreter to treat buffer-name and buffer-file-name as __functions__; without the parentheses, the interpreter would attempt to evaluate the symbols as __variables__.
-> (set 'adf "dsf")
"dsf"
-> __adf__
"dsf"
->(defun demo ()
-> (message "demo")
->)
demo
->demo
**Debugger entered**--Lisp error: (__void-variable__ demo)
->__(demo)__
"demo"
->23
23
->"jdsf"
"jdsf"
->__'df__
df
当在交互式模式中执行lisp代码出现错误时emacs会自动进入调式模式
The error message is generated by__ a built-in GNU Emacs debugger__. We will `enter the debugger'. You get out of the debugger by typing __q__.
---------- Buffer: *Backtrace* ----------
**Debugger entered(表示进入了调式起模式窗口)**--Lisp error: (void-variable kk)
eval(kk)
eval-last-sexp-1(t)
eval-last-sexp(t)
eval-print-last-sexp()
call-interactively(eval-print-last-sexp nil nil
---------- Buffer: *Backtrace* ----------
You read the *Backtrace* buffer __from the bottom up__; it tells you what Emacs did.
===== 对子表求值 =====
如果对一个包含另外一个子表的列表进行求值外部列表可以利用内部子表返回的值进行求值。这也解释了为什么__内部表达式先被求值__因为这些返回值需要被外部表达式利用。如
(+ 2 (+ 3 4))
执行上一列表可在回显区得到结果 9因为 Lisp 解释器先在对内部表达式“(+ 3 4)”进行求值,得到 7再对外部表达式“(+ 2 7)”进行求值得到 9。
===== 变量 =====
在 Emacs Lisp 中__一个符号(symbol)可以指代一个值(value)也可以指代一个函数定义__。两者是有区别的函数定义是一套计算机需要执行的指令而一个值则是不同于其它事物的东西比如一个数或者一个名字。一个符号的值可以是任何 Lisp 表达式通常也被称为“变量”。一个符号还可以同时指代一个值和一个函数定义。__当一个符号与一个值绑定时可以单独求值(例如作为函数的参数)但是当一个符号与一个函数定义绑定时必须将其作为list的第一个元素才能调用该函数。__
===== 参数 =====
参数是指需要提交给函数的信息。不同的函数需要不同的__参数个数__一些函数甚至不需要参数另外不同的函数可能还需要不同的__参数类型__。例如
(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 来代替。
In Lisp, the arguments to a function are __the atoms(如number, string, variable, quoted list, quoted symbol) or lists__ that follow the function. __The values returned by the evaluation of these atoms or lists are passed to the function__. Different functions require different numbers of arguments; some functions require none at all.
当传递错误的参数类型时emacs返回如下错误
---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error:
(wrong-type-argument number-or-marker-p hello)
+(2 hello)
eval((+ 2 (quote hello)))
eval-last-sexp-1(nil)
eval-last-sexp(nil)
call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------
The first part of the error message is straightforward; it says **wrong type argument**. Next comes the mysterious jargon __word number-or-marker-p. This word is trying to tell you what kind of argument the + expected.__
The symbol number-or-marker-p says that the Lisp interpreter is trying to determine whether the information presented it (the value of the argument) is __a number or a marker__ (**a special object representing a buffer position**). What it does is test to see whether the + is being given numbers to add. It also tests to see whether the argument is something called a marker, which is __a specific feature of Emacs Lisp__. (In Emacs, **locations in a buffer are recorded as markers**. When the mark is set with the C-@ or C-<SPC> command, its position is kept as a marker. **The mark can be considered a number**—the number of characters the location is from the beginning of the buffer.) In Emacs Lisp, + can be used to add the numeric value of marker positions as numbers.
The p of number-or-marker-p is the embodiment of a practice started in the early days of Lisp programming. The __p stands for `predicate'__. In the jargon used by the early Lisp researchers, **a predicate refers to a function to determine whether some property is true or false**.
So the p tells us that__ number-or-marker-p is the name of a function__ that determines whether it is true or false that the argument supplied is a number or a marker. Other Lisp symbols that end in p include zerop, a function that tests whether its argument has the value of zero, and listp, a function that tests whether its argument is a list.
Finally, the last part of the error message is the **symbol hello**. __This is the value of the argument that was passed to __+. If the addition had been passed the correct type of object, the value passed would have been a number, such as 37, rather than a symbol like hello. But then you would not have got the error message.
===== 为变量赋值 =====
有好几种方法可以给变量赋值。一种是用“set”或者“setq”函数另一种是用“let”函数。这种过程用术数来说就是__给一个变量“绑定”一个值__。用“set”函数赋值可用如下形式
(set 'flowers '(rose violet daisy buttercup))
对上表求值时,列表“(rose violet daisy buttercup)”将在回显区显示,这是“**set”函数返回的值**。作为一种__附带效应(side-effcet)__符号“flowers”被绑定到这个列表也就是说“flowers”这个变量被这个列表所赋值。另外这种过程也说明了 Lisp 解释器的一种附带效应即赋值是我们人所期望的主效应。__ every Lisp function must return a value if it does not get an error__, but it will only have a side effect if it is designed to have one.)
在实际应用中,几乎每个赋值语句的第一个参数都需要加上撇号“'”由于这种情况很常见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)
...)
如果第一个判断条件返回空(即为假),第一个判断语句将被跳过,然后再对第二个判断条件求值,以此类推。如果某个判断条件返回真,该判断语句将被求值。