Chad Perrin: SOB

4 September 2009

How I Learned to “Love” R6RS Modules

Filed under: Geek — apotheon @ 01:20

Today, I started looking for a portable unit test framework I could use for Scheme development. I haven’t found one yet, but I realized I didn’t know how to use the module system for R6RS to import a unit test framework library, so I decided I should learn that first. After a lot of searching, I managed to cobble together how to make and use libraries in R6RS (the only Scheme revision that has anything like a standardized, cross-implementation module system) by reading code that used libraries and code inside libraries, and reading about modules in places that didn’t really explain them very well.

I decided to write a simplified tutorial, both so my readers may be able to avoid the same difficulty I had and so I can refer back to it later if I manage to forget what I’m doing. Note that I’ve only tested the following with the Ypsilon implementation of R6RS on FreeBSD using its default user shell, tcsh (except that I’ve confirmed (import foo) works on MS Windows). The scheme parts should work the same with other R6RS implementations, and in general it should all work the same on any Unix-like system at least. Any variations are the reader’s responsibility to figure out, though. If you get it working with a different setup than mine, please tell me in comments what (if anything) you had to do differently.

Without further ado, I’ll start by explaining how to create a library module for R6RS.

Creating a Module

I’ll start by creating a module that implements a variation on the perennial “Hello, world!” function in a library. As I do this, a step at a time, you’ll notice that the construction of the module is in some respects from the bottom up.

First, there’s the function:

(lambda ()
  (display "Goodbye, crule world!")
  (newline))

Next, of course, I’ll assign a symbol to it so we can use it when importing the library:

(define emo
  (lambda ()
    (display "Goodbye, crule world!")
    (newline)))

The third step is to import either core or rnrs, the major parts of the standard library. This is necessary because, as with C, you need to import parts of the standard library just to do simple stuff like display (print to standard output):

(import (rnrs))

(define emo
  (lambda ()
    (display "Goodbye, crule world!")
    (newline)))

Fourth, we need to export all the functions in our library we’ll want to use in a script later so that when we import the library we’ll have them available to us:

(export emo)
(import (rnrs))

(define emo
  (lambda ()
    (display "Goodbye, crule world!")
    (newline)))

Finally, we need to name our module:

(library (hello)
  (export emo)
  (import (rnrs))

  (define emo
    (lambda ()
      (display "Goodbye, crule world!")
      (newline))))

This gives us a library that can be imported using the standard module system of R6RS Scheme. Let’s make it more featureful, adding a function for people who prefer a more traditional “Hello, world!” function:

(library (hello)
  (export emo traditional)
  (import (rnrs))

  (define emo
    (lambda ()
      (display "Goodbye, crule world!")
      (newline)))

  (define traditional
    (lambda ()
      (display "Hello, world!")
      (newline))))

Using a Module

Using it is even easier than creating it. To import it to your script, you use the aptly named import:

(import (hello))

After that, you can use the functions exported by the hello module:

(import (hello))

(traditional)

(emo)

That should give you the following output:

Hello, world!
Goodbye, crule world!

It Can’t Find The Library!

First of all, the library file should be called hello.sls or hello.scm, because hello.foobar won’t work. Next, you need to put it in a directory where your Scheme interpreter will look for it. This should be controlled by an environment variable. The name of that variable can change from one implementation to the next, however, so you’ll have to check what it’s called. In Ypsilon (the implementation I’ve been using most), for instance, the relevant environment variable is called $YPSILON_SITELIB.

This environment variable may not exist before you create it. This is an example of how I found that out (using FreeBSD’s standard user shell, tcsh), set the environment variable, and confirmed that I set it properly:

> echo $YPSILON_SITELIB
YPSILON_SITELIB: Undefined variable.
> setenv YPSILON_SITELIB ~/schemelib/
> echo $YPSILON_SITELIB
/home/ren/

Directory paths listed in the Scheme library search path are separated by colons in tcsh, if the environment variable contains more than one directory path. You’ll have to confirm both the name of the environment variable for your specific Scheme implementation and the syntax for setting the environment variable for your specific shell (or just use Ypsilon and tcsh like I do, I suppose).

With that set, I can put any modules I write myself or download from the Internet into schemelib/ inside the home directory for my user account, and (import (foo)) will work fine — assuming a library file named foo.sls, for instance.

Initial Impression

The R6RS module system is impressively simple. It’s also impressively difficult to figure out how one must use it and why one must do things that way, though — largely because it’s not very easy to find out about it with the Google search strings I’ve been using. It seems slightly limited in some ways, and I would think that should be kind of disappointing given the fact that many module systems have been developed for different implementations of Scheme, but I don’t really know anything about those module systems so for all I know they might all suck.

All original content Copyright Chad Perrin: Distributed under the terms of the Open Works License