2.2 简单的表达式

最简单的 Scheme 表达式是常量对象(字面量),比如字符串,数字,符号及列表。Scheme 还支持别的对象类型,不过对大多数程序,这四种就足够了。

数字是常量,如果你输入了一个数字,Scheme 将把它回显给你。下面的例子显示了 Scheme 支持的几种数字类型

123456789987654321 => 123456789987654321
3/4 => 3/4
2.718281828 => 2.718281828
2.2+1.1i => 2.2+1.1i

Scheme 中的数字包括了精确或非精确的整数,有理数,实数及复数。精确的整数及有理数具有任意的精度。它们可以是任意大小。非精确数通表示浮点数,遵循 IEEE 浮点数标准。

Scheme 提供了 + - * / 等算术运算过程。每一个过程接受两个数字参数(实际上可以接受多个参数)。下面的表达式我们称之为“过程应用”(函数调用).

(+ 1/2 1/2) => 1
(- 1.5 1/2) => 1.0
(* 3 1/2) => 3/2
(/ 1.5 3/4) => 2.0

Scheme 使用前缀表示法,甚至包括最常用的算术操作符在内,统统使用前缀表达式。任何的函数调用,不论函数接受0个,1个,2个,或者更多个参数,都写为(procedure arg ...)

函数调用可以嵌套,在这种情况下,内部的表达式先求值。我们可以通过嵌套来组合成更复杂的算术运算

(+ (+ 2 2) (+ 2 2)) => 8
(- 2 (* 4 1/3)) => 2/3
(* 2 (* 2 (* 2 (* 2 2)))) => 32
(/ (* 6/7 7/2) (- 4.5 1.5)) => 1.0

这些例子展示了你可以将 Scheme 用作四则运算的计算器。但是我们在这里不会讨论这些。Scheme 支持相当多的算术函数。现在你可以跳转到 6.4节去一一尝试它们。

对许多任务而言,简单的数字对象就够用了,但是有时候,需要将多个值作为一个独立的对象来操作。在大多数其它语言里,最基本的数据结构是数组。在 Scheme 里,使用最多的是列表 (list)。列表是由括号包围起来的对象序列,例如, (1 2 3 4 5) 是一个数字的列表, ("this" "is" "a" "list")是一个字符串列表。列表里可以包含不同类型的对象, (4.2 "hi"); 列表还可以嵌套 ((1 2) (3 4)).

你可能注意到了,列表看起来很象函数调用,你可能会奇怪:Scheme 怎么区别它们?

在一些情况下,区别很明显。以 (1 2 3 4 5) 为例,开头的是数字 1, 1 显然不可能是一个函数。所以,可能的回答是 Scheme 查看列表的第一个元素,判断它是不是一个函数。这个回答并不准确,我们甚至可以将一个函数调用处理成一个普通的列表,比如 (+ 3 4). 正确的回答是,除非我们明确地告诉 Scheme 将一个列表视为数据,而不是函数调用,否则 Scheme 将把一个列表视为函数调用。我们可以使用“引用”(quote) 来做到这个点。

(quote (1 2 3 4 5)) => (1 2 3 4 5)
(quote ("this" "is" "a" "list")) => ("this" "is" "a" "list")
(quote (+ 3 4)) => (+ 3 4)

quote 强制地将列表视为数据,你可以尝试去掉上面的表达式中的 quote。任何没有 quote 的列表会被视为函数调用,Scheme 会尝试对它求值。

因为quote很常用,Scheme 提供了另外一种简短的缩写,就是单引号 ('),

'(1 2 3 4) => (1 2 3 4)
'((1 2) (3 4)) => ((1 2) (3 4))
'(/ (* 2 -1) 3) => (/ (* 2 -1) 3)

两种写法都是合法的。

quote 表达式并非函数调用,它会阻止对它的子表达式进行求值。这是一种与函数调用完全不同的语法形式。除了函数调用及quote,Scheme 还支持另外几种语法形式。每一种语法形式的求值规则都不一样。幸运的是,这些特殊的语法形式并不多,在本章的后面部分将会看到更多的介绍。

能够被引用的并非只有列表,尝试下面的表达式,然后去掉 quote ,试试直接输入hello会发生什么:

(quote hello) => hello

符号hello也需要被引用起来,不然 Scheme 会认为它是个变量,并尝试对它求值(读取变量的值)。Scheme 的符号和变量与数学表达式里的符号和变量相同或类似。当我们计算数学表达式1 - x的时候,我们认为x是一个变量。当我们思考代数方程式 x2 - 1 = (x - 1) (x + 1) ,我们认为 x 是一个符号。引用一个列表,相当于告诉 Scheme 不要把它当作函数调用。类似地,引用一个符号,相当于告诉 Scheme 不要把它当作变量。符号 (symbol)在程序或方程式中通常就是用来表示变量。

你可能会觉得奇怪,为什么要使用相同的方式来表示列表与函数调用,以及变量与符号。相同的表示法允许将 Scheme 代码表示为数据,可以简化解释器、编译器、编辑器以及其它工具的编写。12.7节里用 Scheme本身写成的 Scheme 解释器将会展示这个特性。很多人认为这是 Scheme 最重要的特性。

数字和字符串也可以引用

'2 => 2
'2/3 => 2/3
(quote "Hi Mom!") => "Hi Mom!"

任何情况下,数字和字符串都被视为常量,所以没有必要引用它们。当然,引用了也不会有什么作用。

现在,我们来学习几个 Scheme 中用来操作列表的过程。有两个过程分别用来获取列表的不同部分:carcdrcar返回列表的第一个元素,而cdr返回列表中剩下的所有元素。(car 及 cdr 的名称起源自第一台在硬件上实现 Lisp 语言的机器,IBM 704)。它们都需要一个非空列表作为参数。

(car '(a b c)) => a
(cdr '(a b c)) => (b c)
(cdr '(a)) => (a)

(car (cdr '(a b c))) => b
(cdr (cdr '(a b c))) => (c)

(car '((a b) (c d))) => (a b)
(cdr '((a b) (c d))) => ((c d))

列表的第一个元素通常被称作列表的 "car", 列表中除了第一个元素外剩下的部分通常被称为列表的 "cdr"。只有一个元素的列表,其 cdr 是空列表 ()

cons过程用来构造列表,它接受两个参数,第二个参数通常是一个列表。如果第二个参数是一个列表,那么 cons 也返回一个列表,它将第一个参数添加到第二个参数的头部,形成一个新的列表。

(cons 'a '()) => (a)
(cons 'a '(b c)) => (a b c)
(cons 'a (cons 'b (cons 'c '()))) => (a b c)
(cons '(a b) '(c d)) => ((a b) c d)

(car (cons 'a '(b c))) => a
(cdr (cons 'a '(b c))) => (b c)
(cons (car '(a b c))
      (cdr '(d e f))) => (a e f)
(cons (car '(a b c))
      (cdr '(a b c))) => (a b c)

"car" 及 "cdr" 通常被用于名词,而 "cons" 通常被用作动词。将一个元素添加到一个列表的开头,形成一个新的列表,称作:将某个元素 cons 到列表里。

上面提到,cons的第二个参数“通常”是一个列表,请注意这个“通常”。过程cons事实上用来构建一个“对偶”(pair),一个对偶的 cdr 部分不一定非要是一个列表,列表事实上是一个对偶(节点)的序列,序列中,每一个对偶的 cdr 部分实际上是指向序列中的下一个对偶的指针。

List

学过数据结构的童子应该看出来了,Scheme 的列表本质上就是链表。最后一个对偶的 cdr 指向一个空列表(相当于null)。如果最后一个 cdr 不是空列表,那么这就不是一个合法的列表。空列表是一个合法的列表。任何列表,只要它的 cdr 部分是一个合法的列表,那么它就是一个合法的列表。

一个不合法的列表用点对来表示,元素之间用一个句点来隔开:

(cons 'a 'b) => (a . b)
(cdr '(a . b)) => b
(cons 'a '(b . c)) => (a b . c)

因为这种表示法,一个对偶经常被称作“点对”(又称点值对)。甚至于,一个合法的列表也可以用点对来表示

'(a . (b . (c . ()))) => (a b c)

list过程类似于cons,只不过它接受任意数量的参数,并且总是返回一个列表

(list 'a 'b 'c) => (a b c)
(list 'a) => (a)
(list) => ()

6.3节提供了更多关于列表及操作列表的过程的信息,现在可以跳转过去,自己先行熟悉。

results matching ""

    No results matching ""