跳轉到內容

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 是假,其他所有值都是真。
華夏公益教科書