跳轉到內容

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 會按某種(未指定)順序評估它們很重要。
華夏公益教科書