跳轉到內容

Scheme 程式設計/庫

來自華夏公益教科書,開放的書籍,面向開放的世界

隨著 R7RS-small 的釋出,該版本在 Scheme 社群中被認為比 R6RS 更好地平衡了極簡主義和實用性,在標準 Scheme 中編寫可移植庫變得更加可能。雖然許多 Scheme 實現尚未實現 R7RS 庫語法,但以下課程至少適用於這些實現

  • Chibi
  • Chicken
  • Gambit
  • Gauche
  • Guile
  • Husk
  • Kawa
  • Larceny
  • MIT Scheme
  • Picrin
  • Sagittarius

R7RS 中的庫通常具有以下結構

(define-library (name ...)
  (import ...)
  (export ...)
  (include "source-file.scm")
  (begin ...))

庫可以在 R7RS 程式開始時使用以下語法載入

(import (name ...))

為了學習如何編寫庫,我們一起編寫一個簡短的示例。我們的庫將使用列表和 define-record-type 實現可變棧資料結構。庫的名稱為 (wikibooks stack),它將匯出的名稱為 stack、stack?、stack-empty?、stack-length、stack-top、stack-push! 和 stack-pop!。這是我們庫的初始樣子。

(define-library (wikibooks stack)
  (import (scheme base))
  (export stack stack? stack-empty? stack-length stack-top stack-push! stack-pop!))

我們幾乎總是想匯入 (scheme base),因為它包括對 define、lambda、quote 和其他基本 Scheme 形式的繫結。我們可能需要匯入其他庫,但現在就足夠了。

為了編寫庫主體,定義必須包含在 (begin ...) 中,或者儲存在單獨的檔案中。對於簡短的庫,包含在 (begin ...) 中並不是一個壞主意,所以這就是我們將要做的。首先,使用 R7RS 中內建的 define-record-type,我們將介紹棧資料結構。現在我們的庫看起來像這樣。

(define-library (wikibooks stack)
  (import (scheme base))
  (export stack stack? stack-empty? stack-length stack-top stack-push! stack-pop!)
  (begin
    (define-record-type <stack>
      (list->stack values)
      stack?
      (values stack->list set-stack-values!))))

這定義了一個名為 <stack> 的不相交資料型別,它只有一個名為 values 的欄位,可以使用 stack->list 獲取,並使用 set-stack-values! 設定。此不相交資料型別的前置謂詞為 stack?,其建構函式為 list->stack。define-record-type 的結構將在以後的 R7RS 課程中全面解釋,它與 SRFI 9 的結構相同。

下一步將定義我們要匯出的過程。為了簡潔起見,以下是一個可能的實現的完整程式碼。

(define-library (wikibooks stack)
  (import (scheme base))
  (export stack stack? stack-empty? stack-length stack-top
          stack-push! stack-pop!)
  (begin
    (define-record-type <stack>
      (list->stack values)
      stack?
      (values stack->list set-stack-values!))
    
    (define (stack . values)
      (list->stack values))

    (define (stack-length s)
      (length (stack->list s)))

    (define (stack-empty? s)
      (null? (stack->list s)))

    (define (stack-top s)
      (car (stack->list s)))

    (define (stack-push! s item)
      (set-stack-values! s (cons item (stack->list s))))

    (define (stack-pop! s)
      (if (stack-empty? s)
          (error "stack-pop!" "Popping from an empty stack"))
      (let ((result (stack-top s)))
        (set-stack-values! s (cdr (stack->list s)))
        result))))

此檔案必須儲存的位置取決於實現。有些只加載以 .sld 結尾的檔案,而有些只加載以 .scm 結尾的檔案。但是,一旦正確安裝,庫就可以載入到 REPL 或程式中,如下所示

> (import (wikibooks stack))
> (define s (stack 1 2 3)
> (stack-push! s 5)
> (stack-pop! s)
5
> (stack-pop! s)
1
> (stack-pop! s)
2
> (stack-pop! s)
3
> (stack-pop! s)
ERROR: stack-pop!: "Popping from an empty stack"

我們已經完成了第一個庫的編寫!現在,如果庫變得更長,繼續縮排兩個巢狀塊可能會很煩人。R7RS 提供了另一種包含原始碼的方法,使用 (include path) 語法。以下是一種替代方法,庫可能看起來像這樣

(define-library (wikibooks stack)
  (import (scheme base))
  (export stack stack? stack-empty? stack-length stack-top
          stack-push! stack-pop!)
  (include "stack.body.scm"))

begin 塊的內容將儲存在檔案 stack.body.scm 中,完全消除了對縮排的需要。

華夏公益教科書