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 中,完全消除了對縮排的需要。