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

Jigsaw.png

Part II

Explicit phasing and the tower of meta-levels

exploding-head.jpg

Part III

Porting libraries between different R6RS implementations

aps_lib_03.jpg

Self-presentation

Who am I?

faccina.jpg

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
#!r6rs
(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

Limitations

Example: compatibility files

$ cat compat.mzscheme.sls
#!r6rs
(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 ...

mantid.jpg

R6RS modules and syntax-rules

A simple example:

#!r6rs
(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)
a=1

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

Phase errors

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

salvador-dali-clock.jpg

Beware of the REPL!

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

but

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

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

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

So everthing is settled, right?

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

joker.jpg

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

DarkTower.jpg

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

list-comprehension.jpg

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)))
sweet-macros.png

Ikarus, Mosh, IronScheme ...

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

tower_of_babel.jpg

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 ...

mule.jpg

Bugs

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

References

and of course in the R6RS document

Acknowledgments

This work would not have been possible without the help of

and many others. Thank you!