[Emacs] Emacs之魂(三):列表,引用和求值策略

回顾

上文我们介绍了Emacs的用法,发现一分钟学会使用它并不是难事,
而且,我们没有让快捷键束缚住,因为Emacs的精髓在于Emacs Lisp中。

本文我们开始探讨Emacs Lisp,不过在这之前我们还要先熟悉一下Lisp的特点和Lisp家族的成员,
随后本文重点分析和介绍了列表,引用和求值策略,
这几个概念,尤其是引用,对学习者来说非常容易引起困惑,
本文采用了不同的角度来描述这些概念。

1. 强大的Lisp

1960年,John McCarthy发表了一篇计算机领域的文章,这是一篇“惊世之作”,
它的作用简直就像欧几里得《几何原本》对几何学的贡献一样。
John McCarthy只用了一些简单的的运算符和函数,构建出了一门图灵完备的编程语言,
称之为Lisp,Lisp是列表处理(List Processing)的简称。
这门语言的关键思想是,不论代码还是数据,都用统一的数据结构(列表)进行表示。

Lisp语言具有很强的表达能力,我们可以用更少的代码做更多的事情。
通常而言,语言具有表达能力就必须提供丰富的内置功能和强大的扩展性。

语言的内置功能指的是语言默认提供的功能,它能减少程序员的重复劳动,帮助他们快速完成工作。
语言的扩展性,指的是当语言内置功能不能满足需要的时候,程序员可以怎样做。
同时具有丰富的功能和强大的扩展性是很困难的,这需要在语言的设计阶段就考虑好,
语言的内置功能越多,就会越复杂,扩展功能的与内置功能的一致性就很难被保证。

现代的高级编程语言,离不开编译器和解释器,
编译器将高级语言的代码转换成更底层的语言,例如C语言或者汇编,
解释器提供了一个运行时环境,直接解释执行高级语言的源代码。

一般而言,编译器是由语言的开发商提供的,使用者并不会参与到编译器的开发工作之中,
如果想要在语言中支持一等函数(first-class function),就必须让语言的开发商改写编译器,
如果需要增加新的类似if-else的控制结构,或者让语言支持面向对象编程,也要改写编译器才行。
因此,语言支持什么功能,以及源代码被如何编译,完全取决于开发商。

而Lisp语言则不同,它允许程序员对编译器进行编程,(元编程
Lisp程序员可以决定代码被如何编译甚至如何被读取,像是半个编译器的开发者一样。

2. Lisp-1和Lisp-2

Lisp语言构成了一个家族,具有成百上千种方言,
用的最多的几种是,Common Lisp,Scheme,Racket,和Emacs Lisp。
其中Scheme的目标是简洁,Common Lisp提供了强大的工业级支持,
Racket提供了一种创造语言和设计实践的平台,Emacs Lisp主要用于Emacs中。

Emacs Lisp更像Common Lisp,它们都是Lisp-2
即同一个符号在不同的上下文中,可以分别用来表示变量和函数,
而Scheme和Racket则只能用来表示同一个实体,称为Lisp-1。

出现Lisp-2,主要是因为有效率方面的考虑,
在Lisp-2中,函数和变量分属不同的名字空间,在不同的环境中,由不同的求值器进行处理。
这样做也使语义更加复杂了,以后的文章中,我们会介绍Emacs Lisp中符号(Symbol)的概念。

3. 列表对象和它的文本表示

列表是Lisp语言中一种常用的数据结构,用来表示一批数据。
例如,由整数123构成的列表对象,Lisp会将它打印为,(1 2 3)
各个列表元素用空格分隔,用圆括号括起来。

然而,在Lisp代码中直接写(1 2 3),并不会创建一个列表对象,
因为Lisp程序也是用括号方式表示的,例如,(+ 1 2)表示对整数12进行加法运算。

那么如何才能创建一个列表对象呢?
我们需要调用list函数,(list 1 2 3),这段代码将会创建一个由整数123构成的列表对象,
这个列表对象打印为(1 2 3)

注意,以上我们严谨的区分了Lisp对象和它的打印结果,
是因为对象和它的文本表示(textual representation)是不同的概念。

例如,在C语言中我们写,

int result = 1 + 2;

我们实际上是用“1”表示了整数1,“1”只是一段文本,是印刷符号,而整数1是一个数学对象,
同样的,“+”是一段文本,它表示了加法运算符。

在Lisp语言中,我们用文本来写程序,而Lisp读取器得到的是Lisp对象,
经过对这些Lisp对象进行计算,得到了计算结果,也是一个Lisp对象,
最终,反馈给我们的是这个对象的文本表示。

4. 字面量和引用

在Lisp中,我们用文本“1”可以直接表示整数1,用“#t”表示真值,
类似的“1”和“#t”,称之为对象的字面量表示(literal representation)。

其它语言中,也提供了广泛的字面量表示法,例如,JavaScript提供了数组和对象字面量,

const obj = {
    x: 1,
    y: [2, 3, 4]
};

这段代码创建了一个JavaScript Object,它的x属性值是1y属性值是一个数组。
字面量表示法,使得我们不必调用new Objectnew Array来创建它。

Lisp中列表对象用的非常多,每次都使用list函数来创建是一件麻烦的事情,
因此,Lisp语言提供了列表对象的字面量写法,我们只需要调用quote就可以了。

(quote (1 2 3))

以上Lisp代码会创建一个打印形式为(1 2 3)的列表对象。

对于嵌套列表,使用quote是非常方便的,

(quote (1 (2 3) ((4 5) (6 7))))

像这样创建列表的方式,称为引用(quoting),
这不同于按引用调用(call by reference)中的“引用”(reference)。

quote还有一个便捷的写法,就是用单引号来表示它,(quote (1 2 3))可以表示为,

'(1 2 3)

我们只需要在列表前加一个单引号即可,因为列表的右括号表明了它在引用这个列表。

5. quote和list

值得一提的是,引用并不保证每次都会重新创建列表。

例如,在Lisp中我们使用define创建函数,

(define (foo)
    '(1 2 3))

然后,用以下方式进行函数调用,注意foo参数个数为0个,

(foo)

多次调用foo,编译器可能返回同一个列表对象。

list则不同,每次调用它会返回一个全新的列表对象,

(define (foo)
    (list 1 2 3))

6. 求值策略

Lisp代码是由表达式构成的,Lisp程序的执行过程就是表达式的求值过程,

(* (+ 1 2) (+ 3 4))

以上表达式的求值结果为21

在程序的列表表示法中,从左到右位于第一个位置的元素,是比较特殊的,
它表示一个函数(function),一个宏(macro),或者一个内置的特殊命令(special form)。
位于其他位置的元素称为参数。

函数被调用的时候,它的每个参数都必须首先被求值,
例如,以上程序中*+都是函数,
在调用乘法函数*时,它的参数(+ 1 2)(+ 3 4)都首先要被求值,分别求值为37
然后再进行乘法运算,结果为21

而宏和内置的特殊命令,并不要求如此,它们有自己的对参数的求值策略。

其中“1”称之为自求值对象,对它进行求值将得到它本身,

1
(eval 1)
(eval (eval 1))

其结果都为1,其它的自求值对象还包括布尔值,字符串,向量,等等。
(+ 1 2)12之前没有quote,是因为它们是自求值对象,(+ '1 '2)(+ 1 2)的计算结果是相等的。

7. 在Emacs中求值表达式

Emacs可以直接求值文本中的Lisp代码,我们只需要将光标定位到列表尾部,
然后按快捷键C-x C-e即可。(指的是按Ctrl+x,然后再按Ctrl+e

我们还可以试试quote和自求值对象,1求值为1'1求值为1

然而''1却求值为(quote 1),是因为''1实际是(quote (quote 1))
它表示用字面量方式创建了一个形如(quote 1)的列表对象。

下文,我们来讨论Emacs Lisp的控制结构和基本的数据类型,
使用Lisp编程是一件有趣的事情。

参考

The Roots of Lisp
Land of Lisp
Lisp in small pieces
An Introduction to Programming in Emacs Lisp
GNU Emacs Lisp Reference Manual

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容