在本页中:
2.4.1 quote引述序对和符号
2.4.2 quote 简写为 '
2.4.3 列表与 Racket 语法

2.4 序对、列表和 Racket 语法

cons 函数实际上接受两个任意值,而不止是一个列表作为第二个参数。 当第二个参数非 empty 且自身不是由 cons 产生时, 其结果会打印为特殊的形式。由 cons 结合的两个值会打印在括号中, 不过它们之间会有个点(即一个两侧为空格的英文句点):

> (cons 1 2)

'(1 . 2)

> (cons "banana" "split")

'("banana" . "split")

因此,由 cons 产生的值并不总是列表。通常,cons 的结果是一个 序对cons? 函数更传统的名字是 pair?, 我们之后会使用这个传统的名字。

rest 对非列表的序对也没什么意义。firstrest 更传统的名字分别是 carcdr。(不过,传统的名字也没什么意义。 你只要记住“a”在“d”前面,cdr发音为“could-er”就好了。)

例如:
> (car (cons 1 2))

1

> (cdr (cons 1 2))

2

> (pair? empty)

#f

> (pair? (cons 1 2))

#t

> (pair? (list 1 2 3))

#t

Racket 的序对数据类型和它与列表的关系,连同打印时的点号记法和滑稽的名字 carcdr 基本上都是历史的奇妙产物。 序对深深地刻印在了 Racket 的文化、规范和实现中,它们因而在语言中得以留存。

你很可能因为非列表序对而犯错,例如不小心弄反了 cons 的参数:

> (cons (list 2 3) 1)

'((2 3) . 1)

> (cons 1 (list 2 3))

'(1 2 3)

有时我们需要特意去使用非列表序对。例如,make-hash 函数接受一个序对的列表, 其中每个序对的 car 为键,cdr 为值。

比非列表序对还要让 Racket 新手感到困惑的是另一种序对的打印约定,其第二个元素为 序对而非列表

> (cons 0 (cons 1 2))

'(0 1 . 2)

通常,序对的打印遵循如下规则:使用点号记法,除非点号后面紧跟着开括号, 而在这种情况下,点号、紧跟的开括号以及匹配的闭括号会被省略。因此 '(0 . (1 . 2)) 会打印为 '(0 1 . 2), 而 '(1 . (2 . (3 . ()))) 则会打印为 '(1 2 3)

2.4.1 quote引述序对和符号

列表在打印时会在前面标一个单引号。但如果列表的元素本身也是列表,那么内部的列表前面则不会有单引号:

> (list (list 1) (list 2 3) (list 4))

'((1) (2 3) (4))

特别来说,对于嵌套列表而言,qoute 形式能让你将列表写成表达式, 其形式基本上与列表的打印形式相同:

> (quote ("red" "green" "blue"))

'("red" "green" "blue")

> (quote ((1) (2 3) (4)))

'((1) (2 3) (4))

> (quote ())

'()

无论列表的引述形式是否会被点号-括号消除规则正规化,qoute 形式都可以与点号形式配合使用:

> (quote (1 . 2))

'(1 . 2)

> (quote (0 . (1 . 2)))

'(0 1 . 2)

自然,任何种类的列表均可以嵌套:

> (list (list 1 2 3) 5 (list "a" "b" "c"))

'((1 2 3) 5 ("a" "b" "c"))

> (quote ((1 2 3) 5 ("a" "b" "c")))

'((1 2 3) 5 ("a" "b" "c"))

如果你用 quote 包裹了标识符,那么其输出形式类似于带有 ' 前缀的标识符:

> (quote jane-doe)

'jane-doe

打印形式为带有单引号前缀的标识符的值,叫做 符号。 同带括号的输出不应与表达式混淆一样,符号的打印也不应当与标识符混淆。 具体来说,除了字母相同外,符号 (quote map) 同标识符 map 或绑定到 map 的预定义函数之间没有任何关系。

实际上,符号固有的值除了其字符常量外再无其它。从这个意义上来说,符号和字符串几乎是一样的, 而它们的主要区别就是打印的形式。函数 symbol->stringstring->symbol 可以在二者之间互相转换。

例如:
> map

#<procedure:map>

> (quote map)

'map

> (symbol? (quote map))

#t

> (symbol? map)

#f

> (procedure? map)

#t

> (string->symbol "map")

'map

> (symbol->string (quote map))

"map"

quote 会自动应用到嵌套的列表内一样,对于括号括住的标识符序列来说, 它也会自动应用到其中的标识符上来创建出符号列表:

> (car (quote (road map)))

'road

> (symbol? (car (quote (road map))))

#t

当符号在以 ' 打印的列表中时,符号前的 ' 会被省略, 因为 ' 已经自动做了这件事:

> (quote (road map))

'(road map)

quote 形式对数值或字符串之类的字面量表达式没有效果:

> (quote 42)

42

> (quote "on the record")

"on the record"

2.4.2 quote 简写为 '

如你所料,你可以将 quote 简写为将 ' 放在表达式前面来引述它:

> '(1 2 3)

'(1 2 3)

> 'road

'road

> '((1 2 3) road ("a" "b" "c"))

'((1 2 3) road ("a" "b" "c"))

在文档中,表达式中的 ' 及其后面的形式会打印为绿色,因为这种组合其实初春贯彻常量表达式。 在 DrRacket 中,只有 ' 会显示为绿色。DrRacket 要更加精准正确,因为 quote 的意思会随表达式的上下文而不同。然而在文档中,我们通常假定标准的绑定是在作用域内的, 因此为了更加清楚,我们就把引述的形式渲染成了绿色。

' 会按其字面意思展开为 quote 形式。如果你在带有 ' 的形式前面再加一个 ',就会看到这一点:

> (car ''road)

'quote

> (car '(quote road))

'quote

' 简写在输出时的行为与输入时一样。REPL 的打印器在打印输出时, 会将符号 'quote 识别为一个两元素列表的第一个元素,此时它会使用 来打印输出:

> (quote (quote road))

''road

> '(quote road)

''road

> ''road

''road

2.4.3 列表与 Racket 语法

现在你已经知道了序对和列表的真相,也见过了 quote, 你已经准备好理解我们简化真正的 Racket 语法的主要方式了。

Racket 的语法并不是直接根据字符流来定义的,而是由两个层次来确定的:

打印和读取的规则是相辅相成的。例如,列表会打印为带括号的形式, 而读取一对括号会产生一个列表。同样,非列表序对会打印为点号记法, 而输入点号实际上会反向执行点号记法的规则,从而获取一个序对。

读取层作用于表达式的一个结果是,你可以在非引述形式的表达式中使用点号记法:

> (+ 1 . (2))

3

这样可行是因为 (+ 1 . (2)) 不过是 (+ 1 2) 的另一种写法。在实践中,使用这种点号记法来编写应用表达式绝对不是个好主意, 这只是 Racket 语法定义方式的一个结果而已。

通常,在括号括住的序列中,读取器只允许在最后一个元素之前使用 .。 然而,在括号括住的序列中,一对 . 也可以出现在单个元素两侧, 只要该元素不是第一个或最后一个几个。这种用法会触发读取器进行转换, 将两个 . 中间的元素移到列表的最前面。这种转换赋予了我们使用通用的中缀记法的能力:

> (1 . < . 2)

#t

> '(1 . < . 2)

'(< 1 2)

这种两个点号的约定并不是传统的,它与非列表序对中的点号记法实际上也没什么关系。 Racket 程序员对中缀约定的使用非常保守—大多用于非对称的二元运算符,例如 <is-a?