Print, Read
对于 Elisp 这种解释型语言,打印和读取不再是像 C 语言那种单纯的 printf, scanf
。Elisp 中的 read
指的是 elisp 解释器读入 elisp 代码并解析的过程,而 print
则指的是 elisp 中的 object 以一种自省的方式打印自身信息的过程。
并不是所有的对象都是可以读入的,但是所有对象都可以输出。不可读入的对象会以“哈希表示法(hash notation)”来表示,它以 #<
开头,以 >
结尾,中间是描述性字符串,如下所示:
#<buffer objects.texi>
Predicate
Predicate 是“断言”的意思,来自离散数学中 谓词 的概念。它在 elisp 中就是布尔函数的意思,可以用来判断某个 Object 是某种类型。这对于弱类型的 elisp 来说非常重要。
Number
数字大致可以分为 3 类:整数,浮点数和字符。
整数可以用多种进制表达,其中对于非十进制数,需要用 #
为开头来表示特殊进制,同时可以用 #<n>r
来指定 n
进制:
44 ; 10 进制
#b101100 ; 2 进制
#o54 ; 8 进制
#x2c ; 16 进制
#24r1k ; 24 进制
对于浮点数,可以使用科学计数法表示, 1500.0, 15e2, 15.0e2, 1.5e3
和 .15e4
都可以用来表示一个浮点数 1500.
。遵循 IEEE 标准,elisp 也有一个特殊类型的值称为 NaN
(not-a-number)。
对于字符,其本质是整数,只是 read
形式不同,需要以 ?
开头,如下所示:
?A ; => 65
?a ; => 97
控制字符可以有多种表示方式,比如 C-i
,这些都是对的:
?\^I ?\^i ?\C-I ?\C-i
他们都是整数 9
。
List
List 是 elisp 中最为常见的数据结构,它的本质在 Lisp 中探讨的不少。
其实 list 还有一个重要特性就是它可以模拟多种数据结构,比如说树、集合、栈等。也就是说,它是很多种数据结构的基础,它的这种模拟是通过各式各样作用于 list 的函数来实现的。比如说对于栈,有 push, pop
函数,对于集合,有 delq, remq
函数。
Elisp 中有一种基于 list 实现的数据结构被称为 association list, alist(关联表),这种结构就类似于编程语言中的 map ,它有两种形式,如下所示:
;; 由 cons 组成 pair
((pine . cones)
(oak . acorns)
(maple . seeds))
;; 由 list 组成 pair
((rose red) (lily white) (buttercup yellow))
其中 car
的部分是 key ,而 cdr
的部分是 value 。我们并不对 key 和 value 的数据类型做限定,形式的差异性并不奇怪,因为它们本质都是 list ,形式的差异只是在 value
到底是一个 list 还是 atom :
(assoc-default "a" '(("a" 97) ("b" 98))) ; (97)
(assoc-default "a" '(("a" . 97) ("b" . 98))) ; 97
还存在一种 property list, plist (属性列表),其含义和 alist 类似,也是一种 map ,但是它不需要将 key-value pair 放到一个 list 中,而是依照 list 中的 index 的奇偶性来判断 key, value 和他们的对应关系:
;; alist
'((?a 97) (?b 98))
;; plist
'(?a 97 ?b 98)
Plist 相对于 alist 结构更更加简单,所以在功能方面要弱一些,但是 plist 是一种 Emacs 专用的数据结构,非常常见。
Array
Elisp 里的 array 包括 string、vector 、char-table 和 bool vector 4 种。
vector 有如下示例:
(setq my-vector [1 2 3]) ; 使用方括号创建向量
(aref my-vector 0) ; 使用 aref 函数通过索引访问向量元素
(aset my-vector 1 5) ; 使用 aset 函数通过索引更新向量元素
(length my-vector) ; 使用 length 函数获取向量的长度
(mapcar #'(lambda (x) (* x 2)) my-vector) ; 使用 mapcar 函数遍历向量
Symbol
符号是一个有名字(是一个字符串)的 object, 我们要求在不能出现重名的 symbol 。需要注意的是,elisp 中的 symbol name 可以包含任何字符且区分大小写,也就是说,像 1+
这种看着非常奇怪的结构,也可能是一个 symbol name 。
Symbol 的唯一性是通过一个叫做 obarray
的结构保障的,这个结构相当于一个 symbol 的集合,每当 elisp 解释器读入一个符号时,通常会先查找这个符号是否在 obarray
里出现过,如果没有则会把这个符号加入到 obarray
里。这样查找并加入一个符号的过程称为是 intern
。 intern
函数可以查找或加入一个名字到 obarray
里,返回对应的符号。默认是全局的 obarray
,也可以指定一个 obarray
。
一个 Symbol 由 4 个组件(Component or Cell)组成,分别是 Print name, Value, Function, Property list
。他们分别表示符号名称,符号对应的变量,符号对应的函数,符号的属性。