跳转到内容

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

华夏公益教科书