Scheme Programming/Libraries

With the release of R7RS-small, which has been more accepted by the Scheme community as a better balance between minimalism and utility than R6RS, the writing of portable libraries in standard Scheme is becoming more possible. While many Scheme implementations have yet to implement the R7RS library syntax, the following lesson will work with at least these implementations:

  • Chibi
  • Chicken
  • Gauche
  • Husk
  • Kawa
  • Larceny
  • Picrin
  • Sagittarius

Libraries in R7RS generally have the following structure:

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

And libraries can be loaded at the beginning of an R7RS program with this syntax:

(import (name ...))

In order to learn how to write a library, we write a short example together. Our library will implement a mutable stack data structure using lists and define-record-type. The name of the library is (wikibooks stack) and the names it will export are stack, stack?, stack-empty?, stack-length, stack-top, stack-push!, and stack-pop!. Here is what our starting library will look like.

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

We almost always want to import (scheme base) because that includes bindings for define, lambda, quote, and other fundamental Scheme forms. It is possible we will need to import other libraries, but this is enough for now.

In order to write the body of the library, the definitions either have to be contained in a (begin ...) or kept in a separate file. For a short library, enclosing in (begin ...) is not a bad idea, so that is what we will do. First, using define-record-type which is built into R7RS, we will introduce the stack data structure. Here is what our library looks like now.

(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!))))

This defined a disjoint data type called <stack> which has a single field called values, which is retrieved with stack->list and set with set-stack-values!. The predicate for this disjoint data type is stack? and its constructor is list->stack. The structure of define-record-type will be fully explained in a future R7RS lesson, and it is identical to that of SRFI 9.

The next step will be to define the procedures we want to export. For brevity, here is the entire code of a possible implementation.

(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))))

Where this file must be saved depends on the implementation. Some will only load a file ending in .sld, and others will only load a file ending in .scm. However once it is installed properly, the library can then be loaded into a REPL or program as

> (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"

And we are done writing our first library! Now, if the library gets much longer, it may be annoying to continue indenting two nested blocks. R7RS provides another way of including source code, using the (include path) syntax. Here is an alternative way the library may look:

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

The contents of the begin block would then be stored in the file stack.body.scm, completely removing the need for indentation.