The R6RS module system

Talk given at:EuroLisp Symposium 2009
By:Michele Simionato
Date: 2009-05-28

Part I

The easy things about the R6RS module system


Part II

Explicit phasing and the tower of meta-levels


Part III

Porting libraries between different R6RS implementations



Who am I?


About me

A hobbyist Scheme programmer

What I have done in Scheme

Part I: the easy part

$ cat my-lib.sls # in most R6RS implementations
(library (my-lib)
 (export a b)
 (import (rnrs)); standard R6RS bindings
 (define a 42)
 (define b 0)
 (display "my-lib instantiated!\n")
;; import it as (import (my-lib))
;; be careful with the names!

Nothing is easy

how to map libraries to the file system is unspecified!

Import syntax

(import (rnrs) (my-lib))
(display (+ a b)) ;=> 42

Import syntax (rename, except)

other easy features


Example: compatibility files

$ cat compat.mzscheme.sls
(library (aps compat)
(export printf format pretty-print)
(import (rnrs) (only (scheme) printf format pretty-print)))

$ cat compat.ypsilon.sls
(library (aps compat)
(export printf format pretty-print)
(import (rnrs) (core))
(define (printf format-string . args)
  (display (apply format format-string args))))

Let's start with macros now

They are not so scary ...


R6RS modules and syntax-rules

A simple example:

(library (show)
 (export show)
 (import (rnrs) (only (aps compat) printf))
 (define-syntax show
   (syntax-rules ()
    ((_ x) (printf "~a=~a\n" 'x x)))))

Macro usage

99.9% of times there are no problems with syntax-rules macros:

> (import (show))
> (define a 1)
> (show a)

I will show an issue with a second order syntax-rules macro later on

Where is the problem?

> (let ()
    (define a 42)
    (define-syntax m (lambda (x) a))
error: identifier a out of context

Because of phase separation

(let ()
 (define a 42)                    ; run-time
 (define-syntax m (lambda (x) a)) ; macro-def-time

Phase errors

One must be careful not to mix expand-time and run-time


Beware of the REPL!

$ mzscheme # or larceny
> (define a 42)
> (let-syntax ((m (lambda (x) a))) m)
reference to undefined identifier: a


$ ikarus # or ypsilon
> (define a 42)
> (let-syntax ((m (lambda (x) a))) m)

No phase separation

Phase separation is not ubiquitous; for instance Guile 1.8 (or Emacs Lisp) have no phase separation:

guile> (let ()
         (define a 42)
         (define-macro (m) a)

(the next version of Guile will have phase separation and some support for R6RS Scheme).

To cope with phase separation

Put the helper object (value, function, macro) in a different module and import it at expand time

> (import (for (my-lib) expand))
> (let-syntax ((m (lambda (x) a))) m)

So everthing is settled, right?

Not really, since there are a few R6RS surprises ...


The R6RS specification is loose

An example:

(import (for (only (my-lib) a) expand))
(display (let-syntax ((m (lambda (x) a))) m))
(display a)

Lack of phase specification

Part II

Fasten your seatbelts now ...

The Dark Tower of meta-levels


Meta-levels in macros

we saw that the right hand side of macro definition refers to names which are one phase (meta-level) up

An example at meta-level 2

(import (for (only (my-lib) a) (meta 2))
        (for (only (my-lib) b) (meta 1)))
(define-syntax m1       ;; level 0
 (lambda (x1)           ;; level 1
    (define-syntax m2   ;; level 1
       (lambda (x2) a)) ;; level 2
    (+ m2 b)))          ;; level 1
(display                ;; level 0
  m1                    ;; level 1
)                       ;; level 0

Positive meta-levels


The Dark Side of the Tower

(define-syntax very-static-table
 (syntax-rules ()
   ((_ (name value) ...)
     (syntax-rules (<names> name ...)
       ((_ <names>) '(name ...))
       ((_ name) value) ...))))

(define-syntax color
 (very-static-table (red 0) (green 1) (blue 2)))

(display (color <names>)) ;=> (red green blue)
(display (color red)) ;=> 0

Negative meta-levels

(define-syntax very-static-table      ;; level 0
 (syntax-rules ()                     ;; level 1
   ((_ (name value) ...)              ;; level 1
     (syntax-rules (<names> name ...) ;; level 0
       ((_ <names>)                   ;; level 0
         '(name ...))                 ;; level -1
       ((_ name)                      ;; level 0
         value)                       ;; level -1

Needs (import (for (only (rnrs) quote) (meta -1)))

I had to fight with meta-levels

(import (rnrs) (for (rnrs) (meta -1))
(for (sweet-macros helper1) (meta -1) (meta 0) (meta 1)))

Ikarus, Mosh, IronScheme ...

Such implementations do not need to worry about the Dark Tower. I think they will have a great future!


R6RS: an unhappy compromise

You get the worse of two worlds: writers of portable code

Part III: my experience

Porting macro-rich R6RS libraries can be a rather heavy task ...



I have found many bugs in different R6RS implementations while porting my sweet-macros library:

All fixed within hours!

Other difficulties

More non-portable behavior


and of course in the R6RS document


This work would not have been possible without the help of

and many others. Thank you!