跳转到内容

Scheme 编程/数字和表达式

来自维基教科书,开放世界中的开放书籍
Scheme 编程
 ← 简单表达式 数字和表达式 进一步的数学 → 

在本节中,我们将继续使用数字,因为它们熟悉且易于推理。我们还将介绍 Scheme 类型系统,并更仔细地考虑 Scheme 表达式是如何求值的。

首先,我们什么时候说一个 Scheme 对象是“数字”或“布尔值”?这些名称表示对象的类型。一般来说,了解对象的类型可以告诉我们有关如何使用它的信息。如果我们知道 都是布尔值类型,我们知道逻辑的 AND 操作是在 上定义的;也就是说,( AND ) 的值将是有意义的。我们说这种有意义的表达式是类型正确的。另一方面,如果 数字类型,我们无法求值 ( AND ),因为我们不知道如何理解 AND,除非它的两个参数都是布尔值。在这种情况下,表达式是类型错误的,它的值未定义。

Scheme 是一种强类型 语言,这意味着类型错误的表达式是被禁止的;求值一个类型错误的表达式会导致 Scheme 报告错误。[1] 例如,我们可以期望 Scheme 拒绝以下表达式

> (+ 10 #t)
;; Error: (+) bad argument type - not a number: #t

从错误消息中,我们可以推断出 Scheme 过程 + 期望它的参数是数字。由于 #t 是一个布尔值,表达式 (+ 10 #t) 是类型错误的;无法获得有意义的值,因此程序的执行会停止。这样,Scheme 可以防止我们可能在执行继续使用伪造的值时遇到的其他意外情况。

我们如何获取有关 Scheme 对象的类型信息?Scheme 类型由类型谓词定义。这些是过程,它们给定任何 Scheme 对象作为参数,如果对象具有特定类型,则返回 #t,否则返回 #f。因此,通过应用类型谓词 foo? 来表示类型 foo,我们可以将所有 Scheme 对象集划分为 foo 类型的对象和其他对象。

到目前为止,我们已经使用过数字和布尔值,它们具有类型谓词 number? 和,正如你可能猜到的,boolean?

> (number? 10)
#t
> (number? #t)
#f
> (boolean? 3)
#f
> (boolean? #f)
#t

Scheme 保证[2] 基本类型(包括数字和布尔值)是不相交的。这意味着,例如,没有 Scheme 对象既是数字又是布尔值;它可能是一个或另一个,也可能都不是,但它不能同时是两者。用类型谓词术语来说,不存在 Scheme 对象 x 使得 (number? x)(boolean? x) 都为 #t

我们不会在这本书中经常使用像 number? 这样的基本类型谓词,但理解它们如何定义 Scheme 类型系统非常重要。它们在核心 Scheme 和库过程中的使用非常频繁,例如,为了确保参数是类型正确的。

我们在上一节中查看了几个简单数值程序的示例。然而,到目前为止,我们看到的程序只使用了整数。Scheme 拥有一个丰富的数字类型系统,称为数值塔,它使我们能够使用有理数、实数和复数进行计算。

> (+ 9/10 4/5)
17/10
> (* 2.423 -5.39245)
-13.06590635
> 2.762e8
276200000.0      ; 2.762 * 10^8
> (- 4+2i 1+7i)
3-5i
> (* 3+5i (+ 1.3 (/ 3 2)))
8.4+14.0i

正如这些示例所示,Scheme 还提供了多种编写数字的方式。有理数以 a/b 的形式编写,实数可以以指数(“科学”)表示法编写为 men(给出值 ),复数以直角坐标形式编写为 a+bi(或 a-bi),其中 a 是实部,b 是虚部。[3]

正如我们在最后的嵌套示例中看到的,Scheme 允许我们在不手动将它们转换为通用形式的情况下混合使用不同类型的数字。

Scheme 还提供了大量数值过程。以下是一些示例

> (gcd 36 60)  ; greatest common divisor
12
> (min 3 4.7 2.1)  ; min gives the minimum of its arguments
2.1
> (log 18)  ; natural logarithm of 18
2.89037175789616
> (floor-remainder 15 8)  ; integer division remainder (modulo)
7

有关数值过程的完整列表,请参阅 R7RS § 6.2.6。

表达式

[编辑 | 编辑源代码]

到目前为止,我们已经看到 Scheme 评估了许多表达式(希望你已经在 Scheme 解释器中尝试过它们)。但是,我们对 Scheme 表达式的含义含糊其辞。很容易猜到 (+ 2 3) 通过将数字 2 和 3 相加来评估,但当我们面对复杂的嵌套或包含比自然数或加法更不熟悉的事物的表达式时,猜测是不够的。我们需要一个模型来评估我们一直在看到的 Scheme 表达式。以下是简化版本,但对于本章中我们将要看的程序来说是正确的。

表达式 (+ 2 3) 似乎有点微不足道,但让我们一步一步地分析它。这个表达式是运算符 + 对操作数 23应用。要评估应用程序,

  • 首先,评估运算符和操作数。
  • 然后,将作为运算符值的程序应用于操作数的值。

我们在 "Scheme 简介" 中的第一个示例中看到,要求 Scheme 评估一个数字会将该数字作为值返回给我们。这个观察结果给了我们评估数字的一般规则

评估数字:数字是自评估的。

有了这条规则,评估操作数就微不足道了。现在我们需要运算符 + 的值。这个值是一个过程对象。这个值是不透明的;也就是说,它是 Scheme 给我们的用于执行加法的“黑盒子”,我们无法看到它的内部运作。然后,我们可以将这个值应用于操作数的值来获得 5,即整个表达式的值。

一个更有趣的例子是表达式 (* (+ 2 3) (- 7 5))。在这里,运算符是 *,操作数是 (+ 2 3)(- 7 5)。要找到这个应用程序的值,我们必须评估这些子表达式,它们本身就是应用程序!以下是可能的评估步骤序列之一:[4]

(* (+ 2 3) (- 7 5))
  = (* 5 (- 7 5))
  = (* 5 2)
  = 10

更复杂的表达式通过完全相同的过程来评估;请参见下面的练习 1。只要我们处理完全由应用程序构建的表达式,那么我们就知道如何评估它们;我们递归地应用我们的评估规则,直到表达式被完全评估。

练习 (解决方案)
  1. 给出一个以下表达式的评估步骤序列。
    (* (- 7 (/ 9 3)) (* 10 (- 15 3) (+ 4 2)))
    

笔记

[edit | edit source]
  1. 一些流行的语言,包括 JavaScript 和 PHP,是弱类型的。粗略地说,这些语言允许在强类型语言中类型错误的组合。这通常是通过自动将一种类型的值转换为另一种类型的值来实现的;例如,JavaScript 表达式 4 + "five"(它组合了一个数字和一个字符字符串)计算为字符串 "4five"。在这里,数值操作数 4 已被隐式转换为字符串 "4"。Scheme 与其他强类型语言一样,不执行此类魔法;值必须显式转换。
  2. R7RS § 3.2
  3. Scheme 还提供了 极坐标 表示复数。我们可以写 r@t 来表示模为 r,辐角为 t(以弧度表示)的复数。
  4. 我们实际上并不知道操作数的评估顺序。目前,这没有任何区别,但了解 Scheme 会按某种(未指定)顺序评估它们很重要。
华夏公益教科书