跳轉到內容

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 會按照某種(未指定的)順序對它們進行求值。
華夏公益教科書