跳转到内容

Scheme 编程/数字和表达式

来自维基教科书,自由的教科书,共建一个开放的世界
Scheme 编程
 ← 简单表达式 数字和表达式 高等数学 → 

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

首先,当我们说一个 Scheme 对象是“数字”或“布尔值”时,我们的意思是?这些名称表示对象的类型。一般来说,了解对象的类型可以让我们了解如何使用它。如果我们知道都属于布尔值类型,我们知道逻辑运算中的 AND 运算是在上定义的;也就是说,( 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)))
    
  1. 一些流行的语言,包括 JavaScript 和 PHP,是弱类型的。粗略地说,这些语言允许在强类型语言中类型不匹配的组合。这通常是通过自动将一种类型的值转换为另一种类型的值来实现的;例如,JavaScript 表达式 4 + "five"(它结合了数字和字符字符串)求值为字符串 "4five"。在这里,数值操作数 4 已经被隐式地转换为字符串 "4"。Scheme 与其他强类型语言一样,不执行任何这种魔法;必须显式地转换值。
  2. R7RS § 3.2
  3. Scheme 还提供了 极坐标系 来表示复数。我们可以用 r@t 来表示模为r,辐角为t(以弧度表示)的复数。
  4. 我们实际上不知道操作数的求值顺序。目前,这没有区别,但重要的是要理解 Scheme 会按照某种(未指定的)顺序对它们进行求值。
华夏公益教科书