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 中,完全消除了对缩进的需要。