跳转到内容

Scheme 编程/Scheme 简介

来自维基教科书,开放的书籍,为开放的世界
Scheme 编程
 ← 使用 Scheme 解释器 Scheme 简介 Scheme 数据类型 → 

现在我们已经了解了如何运行 Scheme 解释器,让我们通过一些简短的例子看看我们可以用它做什么。我们不会深入研究这些程序。目前,我们的目的是让读者对 Scheme 有一个初步的了解。

Scheme 解释器会评估用户输入的 Scheme 表达式。在我们的解释器运行后,我们首先评估一个非常简单的表达式。

> 4
4

当我们输入 '4' 并按下回车键时,我们要求我们的 Scheme 解释器评估表达式 '4'。不出所料,4 评估为 4。

接下来,我们尝试一些基本的算术运算。

> (+ 3 6)
9
> (* 18 5)  ; multiplication
90
> (- 4 7)
-3

这些例子说明了 Scheme 语法中的一些重要事项。首先,语法是完全括号化的;上面表达式中的括号是必需的,不能随意添加括号,否则会改变表达式的含义。例如,以下会导致解释器报错

> ((+ 3 6))
;; Error: application of non-procedure 9

其次,我们看到 Scheme 使用前缀表示法。在每个表达式中,运算符 (+, *, -) 位于操作数(数字)之前。这与我们通常使用的数学表示法(“中缀”)不同,在中缀表示法中,运算符位于操作数之间。例如,以下不是有效的 Scheme 表达式

> (8 * 5 - 3)
;; Error: application of non-procedure 5

然而,将其转换为 Scheme 很容易

> (- (* 8 5) 3)
37

请注意,我们在这里使用表达式 (* 8 5) 作为另一个表达式的操作数!这是有效的 Scheme,我们将在所有地方看到这样的嵌套表达式。

Scheme 允许我们为值命名以便在其他表达式中使用

> (define x 20)
> x
20
> (define y (+ x 4))
> (* y 2)
48

定义 (define x 20) 使解释器将 x 定义为 20 的值。当 x 出现在定义后的代码中时,它将被此值替换。然后,我们可以在 y 的定义中使用 x

我们还可以定义过程。与内置运算符 +- 等类似,过程接受一定数量的参数(也称为参数),并计算一个值。

> (define (square x)
    (* x x))
> (square 5)
25

在这里,我们定义 square 表示一个接受一个参数 x 并返回 (* x x) 值的过程。此定义的形式与我们之前看到的略有不同;在这里,定义的名称出现在括号中,后面是过程参数的名称。为了使用这个新定义的过程,我们评估 (square 5)

这是一个更复杂的过程,它使用 海伦公式 计算边长为 的三角形的面积

> (define (heron a b c)
    (let ((s (/ (+ a b c) 2)))
      (sqrt (* s (- s a) (- s b) (- s c)))))
> (heron 4 13 15)
24

在这个过程中,我们需要使用值 ,定义为

,

四次。如果我们要对 的每次出现都使用 (/ (+ a b c) 2) [1],那么 heron 的定义将很繁琐且难以阅读。我们使用 let 形式来解决这个问题,它允许我们暂时将名称 s 与此值相关联。[2]

我们可以比较值,或者测试它们是否具有某些属性。

> (= 4 (+ 3 1))
#t
> (< 9 7)
#f
> (positive? 5)
#t
> (even? 5)
#f

Scheme 返回给我们的 #t#f 值是什么?你可能已经猜到了,它们是分别代表“真”和“假”的布尔值。

positive? 这样的谓词是接受一个参数并返回布尔值的过程。在 Scheme 中,通常会给谓词取以 '?' 结尾的名称;因此,我们有 even?negative? 以及许多其他谓词。

过程可以是 递归 的。以下是一个经典的例子。

> (define (factorial n)
    (if (= n 0)
        1
        (* n (factorial (- n 1)))))
> (factorial 5)
120

评估 (factorial 5) 会给出 的值,也就是 ,我们可以递归地表示为 。更一般地说,我们说 的阶乘如果 为零则为 1,否则为 乘以 的阶乘。这正是 factorial 的定义在 Scheme 语言中所述的内容

(define (factorial n)
  (if (= n 0)                      ; is the argument 0?
      1                            ; then the answer is 1
      (* n (factorial (- n 1)))))  ; otherwise, it's n * (n - 1)!

递归是 Scheme(以及一般计算机科学)中一个重要的概念,我们将在后面的章节中更深入地讨论。

factorial 定义中的另一个新事物是使用 if 表达式,它会先评估第一个参数,然后根据第一个参数的值为真还是假,分别评估第二个或第三个参数。如果第一个参数的值为真,那么我们会得到 if 表达式的第二个参数;否则,我们会得到第三个参数。[3]

> (if #t 1 0)
1
> (if (> 2 3) 1 0)
0
练习 (解答)
  1. 为什么评估 ((+ 3 6)) 会导致 Scheme 报告“应用非过程”?
  1. 在之前的示例中,我们给 + 传递了两个参数。然而,这里我们传递了三个参数。这是正确的吗?一般来说,如果我们给过程传递的参数过少或过多,Scheme 会报告错误。但是,+ 是一个 可变参数 过程,这意味着它可以接受任意数量的参数。表达式 (+ a b c) 等价于 (+ (+ a b) c)-*/ 也是可变参数过程。
  2. 如果你熟悉使用“局部变量”的编程语言,你可以将 let 绑定的名称视为与局部变量相同的东西。
  3. 目前,我们假设“真或假”是指布尔值,即 #t#f。实际上,每个 Scheme 对象都有一个布尔值;即 #f 是假,其他所有值都是真。
华夏公益教科书