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.

15 Comments

  1. It’s “cruel”

    Comment by Anonymous — 5 September 2009 @ 11:26

  2. I’m learning scheme at the moment, focusing on r6rs. I’m using plt-scheme under both Windows and Debian. You are certainly right about how hard it is to find introductory information on using the library system from r6rs. Most writing about it seems to be just ‘reference documentation’, which is always just a description of the language with little explanation of its purpose and no justification, it took a good day or two to get the hang of it. As for using r6rs libraries with other implementations, under plt-scheme you have to install them into the existing library system, which maps the library name to a heirarchy of directories with the actual code in a file called ‘main.ss’. For instance, I knocked up a queue library which lives in ‘C:\Users\alistair\AppData\Roaming\PLT Scheme\4.2.1\collects\queue\main.ss’ under Win 7, with the tests in ‘C:\Users\alistair\AppData\Roaming\PLT Scheme\4.2.1\collects\queue\test\main.ss’. Then it’s just (import (queue)) or (import (queue test)) to use.

    I do like the r6rs module system though. It’s nicely minimal, simple, and it doesn’t try to impose a naming convention, except that it assumes that libraries are stored in a tree, which is a pretty sensible assumption given that one of the main activity when using a library is asking the computer to find it. A bit like Java packages without the insistence on using reversed URIs for everything.

    Comment by Alistair — 5 September 2009 @ 11:32

  3. Thanks for this intro. Correct me if I’m wrong, but it looks like you’re missing a close parenthesis after you added the “library” statement.

    Comment by Chip Camden — 5 September 2009 @ 11:33

  4. BTW, this seems a lot simpler than CL’s package concept.

    Comment by Chip Camden — 5 September 2009 @ 11:34

  5. You’re right — there was a closing parenthesis missing (and I had the same problem in the example where I added the traditional function). I fixed it. Thanks for pointing it out.

    How does CL’s packaging work?

    Comment by apotheon — 5 September 2009 @ 01:10

  6. I haven’t taken the time to figure it out yet. From what I’ve read you declare the code (in-package packagename) and then (use packagename) — but I have no idea where the files need to live. I’ll let you know if I ever explore this further.

    Comment by Chip Camden — 5 September 2009 @ 02:51

  7. Anonymous:

    It’s a “joke”.

    (import (humor))
    
    (define joke (and
                   (call-it "emo")
                   (mispel "cruel" "crule")))
    
    (define reaction
      (lambda (sense)
        (if sense
            (laugh-at joke)
            "whoosh")))
    
    Alistair:

    I rather like the simplicity of of the module system, too. It’s certainly easier to create a useful and usable library for it than for most other languages with module systems. My complaints at the moment are a lack of a good place to go for libraries (that I’ve been able to find), the relative dearth of libraries in general (probably a big reason for the first problem), and the lack of clear explanations of the sort you’ve noted don’t seem to exist.

    Comment by apotheon — 5 September 2009 @ 02:51

  8. I love the self-referential mispelling in your code snippet in that last comment.

    Comment by Chip Camden — 5 September 2009 @ 02:54

  9. Thanks.

    I fixed it up a little more since you probably read it, but the “mispeling” is still there.

    . . . and I look forward to what you report about CL packaging.

    Comment by apotheon — 5 September 2009 @ 02:57

  10. For me,

    (reaction (sense joke)) => (made my day)

    which was the result returned by (laugh-at joke)

    Comment by Chip Camden — 5 September 2009 @ 03:07

  11. Common lisp packages are not libraries so much as namespaces. Libraries are usually handled by a system called ASDF (Another System Definition Facility); this allows you to create a spec file that describes just how to load your package, how to test it, and what packages it requires.

    Comment by TheQuux — 5 September 2009 @ 05:59

  12. I’ve heard of ASDF, now that you mention it. Doesn’t that do something akin to CPAN — or am I thinking of some other string of characters?

    Comment by apotheon — 5 September 2009 @ 07:12

  13. The standard defines a library by ‘Each library contains definitions and expressions. It can import definitions from other libraries and export definitions to other libraries.’, which is all you need to know to use them, but doesn’t really help with understanding how they fit in with scheme’s model of computation. Something I only just realised about the r6rs library system is that the way to think about a library import is that it defines the global environment used when evaluating the content of the library (or a top-level program where the library form is absent, but it is basically the same thing).

    It’s notable that one can create these environments as computational objects using the (environment import-spec …) procedure, and then eval expressions in the new environment you have created; a lovely way of integrating the library system with the evaluator and giving the user some control over evaluation. I think there must be some neat stuff like debuggers that could be implemented by evaluating the expressions under consideration in the environment defined by a custom library.

    Comment by Alistair — 14 September 2009 @ 01:55

  14. Alas, you’re moving somewhat outside the range of how much Scheme I actually know. You’ve given me some things to think about as I learn more of the language, though. Thanks, Alistair.

    Comment by apotheon — 14 September 2009 @ 02:18

  15. Here is an intro to libraries in R6RS:

    http://www.scheme.com/tspl4/libraries.html#./libraries:h0

    Comment by Grant Rettke — 27 September 2009 @ 10:13

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

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