Title

R6RS Library Syntax

Authors

Matthew Flatt and Kent Dybvig

Status

This SRFI is being submitted by members of the Scheme Language Editor's Committee as part of the R6RS Scheme standardization process. The purpose of such ``R6RS SRFIs'' is to inform the Scheme community of features and design ideas under consideration by the editors and to allow the community to give the editors some direct feedback that will be considered during the design process.

At the end of the discussion period, this SRFI will be withdrawn. When the R6RS specification is finalized, the SRFI may be revised to conform to the R6RS specification and then resubmitted with the intent to finalize it. This procedure aims to avoid the situation where this SRFI is inconsistent with R6RS. An inconsistency between R6RS and this SRFI could confuse some users. Moreover it could pose implementation problems for R6RS compliant Scheme systems that aim to support this SRFI. Note that departures from the SRFI specification by the Scheme Language Editor's Committee may occur due to other design constraints, such as design consistency with other features that are not under discussion as SRFIs.

This SRFI is currently in ``withdrawn'' status. To see an explanation of each status that a SRFI can hold, see here. It will remain in draft status until 2006/04/02, or as amended. To export input on this SRFI, please mailto:srfi minus 83 at srfi dot schemers dot org. See instructions here to subscribe to the list. You can access previous messages via the archive of the mailing list.

Abstract

The module system presented here is designed to let programmers share libraries, i.e., code that is intended to be incorporated into larger programs, and especially into programs that use library code from multiple sources. The module system supports macro definitions within modules, allows macro exports, and distinguishes the phases in which definitions and imports are needed. This SRFI defines a standard notation for libraries, a semantics for library expansion and execution, and a simple format for sharing libraries.

Issues

Rationale

This standard addresses the following specific goals:

It does not address the following:

Specification

Library Form

A library declaration contains the following elements:

Library Form Syntax

A library definition is written with the library form:

   (library <lib-path> <language>
     <body>)

The syntax of <body> depends on the specific <language>. An implementation may allow a reference to another library as a <language>, but this SRFI defines only the behavior when <language> is "scheme://r6rs". Future standards may define the meaning of other <language> forms, but it is expected that <body> will follow Scheme's lexical conventions, so that read can process any library declaration.

Example:
  (library "hello" "scheme://r6rs"
    (display "Hello World")
    (newline))

For "scheme://r6rs" as <language>, the grammar of <body> is as follows:

 <body> = <impexp-form>* <comdef-form>*

 <impexp-form> = (import <import-spec>*)
               | (export <export-spec>*)

 <comdef-form> = <command or definition>
               | (indirect-export <indirect-spec>*)
               | (begin <comdef-form>*)
               | <derived-comdef>

The import form imports bindings into the library, and the export form exports bindings from the library. A <command or definition> introduces a local definition or executes an expression for its side-effects, and the grammar for <command or definition> is the same as in R5RS. No identifier can be imported multiple times, defined multiple times, or both defined and imported. The indirect-export form declares that certain macros might expand to uses of certain unexported bindings.

A <derived-comdef> is a macro call that expands into a <comdef>.

An <import-spec> imports bindings for use at a specific phase:

 <import-spec> = <import-set>
               | (for <import-set> <import-phase>*)
 <import-phase> = run
                | expand

An unadorned <import-set> imports for run time. A for form imports for either run time, expansion time, or both. The listed <import-phase>s must be distinct.

An <export-spec> is merely an <export-set>:

 <export-spec> = <export-set>

An <indirect-spec> contains an identifier for a macro defined in this library followed by a list of other identifiers that are defined within this library:

 <indirect-spec> = (<identifier> <identifier>*)

The meaning of an <indirect-spec> is that the expansion of a macro bound to the first identifier can contain references to the remaining identifiers. If the first identifier is exported from the library, then the other identifiers are potentially accessible outside the library. If an identifier is not exported and not indirectly exported with an exported macro, then accessing the identifier from outside the library signals an error. (If access is possible at all, then it is only possible through a macro whose expansion is inconsistent with the declaration of indirect exports.) If the first <identifier> in an <indirect-spec> is not defined as a macro within the library, or if any of the other <identifiers>s is not defined within the library, then an error is signalled.

An <import-set> names a set of bindings from another library, and gives them local names for the importing libraries:

  <import-set> = <lib-path>
               | (only <X-set> <identifier>*)
               | (except <X-set> <identifier>*)
               | (add-prefix <X-set> <identifier>)
               | (rename <X-set> (<identifier> <identifier>)*)

The ultimate <lib-path> names the exports of a particular library. Initially, the internal names for the imports are the same as the external names from the exporting library, and forms such as add-prefix and rename adjust the internal set of internal names. More specifically:

An <export-set> names a set of imported and locally defined bindings, and gives them external names for exporting:

 <export-set> = <identifier>
              | (rename (<identifier> <identifier>)*)

In an <export-set>, an <identifier> names a single binding defined within the library or imported, where the external name for the export is the same as the name of the binding within the library. A rename set exports the binding named by the first <identifier> in each pair, using the second <identifier> as the external name.

Examples for various <import-spec>s and <export-spec>s:
  (library "stack" "scheme://r6rs"
    (export make push! pop! empty!)

    (define (make) (list '()))
    (define (push! s v) (set-car! s (cons v (car s))))
    (define (pop! s) (let ([v (caar s)])
                       (set-car! s (cdar s))
                       v))
    (define (empty! s) (set-car! s '())))


  (library "balloons" "scheme://r6rs"
    (export make push pop)

    (define (make w h) (cons w h))
    (define (push b amt) (cons (- (car b) amt) (+ (cdr b) amt)))
    (define (pop b) (display "Boom! ") 
                    (display (* (car b) (cdr b))) 
                    (newline)))


  (library "party" "scheme://r6rs"
    (import (only "stack" make push! pop!) ; not empty!
            (add-prefix "balloons" balloon:))
    ;; Total exports: make, push, 
    ;;                push!,
    ;;                make-party,
    ;;                pop!, pop-again!
    (export (rename (balloon:make make)
	            (balloon:push push))
	    push!
	    make-party
	    (rename (party-pop! pop!)))

    ;; Creates a party as a stack of balloons, starting with
    ;;  two balloons
    (define (make-party)
      (let ([s (make)]) ; from stack
        (push! s (balloon:make 10 10))
        (push! s (balloon:make 12 9))
        s))
    (define (party-pop! p)
      (balloon:pop (pop! p))))


    (library "main" "scheme://r6rs"
      (import "party")

      (define p (make-party))
      (pop! p)        ; displays "Boom! 108"
      (push! p (push (make 5 5) 1))
      (pop! p))       ; displays "Boom! 24"

Examples for macros and phases:
  (library "helper" "scheme://r6rs"
    (export find-dup)

    (define (find-dup l)
      (and (pair? l)
           (let loop ((rest (cdr l)))
             (cond
              [(null? rest) (find-dup (cdr l))]
              [(bound-identifier=? (car l) (car rest)) (car rest)]
              [else (loop (cdr rest))])))))


  (library "let-values" "scheme://r6rs"
    (import (for "helper" expand))
    (export let-values)

    (define-syntax let-values
      (lambda (stx)
        (syntax-case stx ()
          [(_ [(id ...) expr] body0 body ...)
           (not (find-dup (syntax-object->list (syntax (id ...)))))
           (syntax (call-with-values (lambda () expr) 
                                     (lambda (id ...) body0 body ...)))]))))


  (library "let-div" "scheme://r6rs"
    (import "let-values")
    (export let-div)

    (indirect-export (let-div quotient+remainder))
    (define (quotient+remainder n d)
      ;; recompute, for now...
      (values (quotient n d) (remainder n d)))
    (define-syntax let-div
      (syntax-rules ()
       [(_ n d (q r) body0 body ...)
        (let-values [(q r) (quotient+remainder n d)]
          body0 body ...)])))

Library Macro Expansion

For each import form, all imported bindings are available for processing of <comdef-form>s. Variables and macros imported into the expand-time phase with (for .... expand) are available for use within macro definitions. More precisely, libraries imported for run time must be visited (see Library Semantics) if they have not been visited already, and libraries imported for expand time with (for .... expand) must be invoked (see Library Semantics) if they have not been invoked already.

To discover macro definitions within the library, <comdef-form>s are partially expanded first to last; expansion proceeds only far enough to determine whether it is a definition, begin form, or something else. If the <comdef-form> is a macro definition, then the macro becomes available for use in expanding later <comdef-form>s. If the <comdef-form> is a begin form, the content forms are sliced into the library top-level in place of the begin form, and partial expansion proceeds with the newly spliced forms.

After all macro definitions have been discovered, macro expansion resumes for each <comdef-form>, using all discovered macros. In this way, macros defined later can be used to generate non-definition <comdef-form>s or used on the right-hand side of definition <comdef-form>s. An error is signalled if expansion of a <comdef-form> produces a definition in this second phase (i.e., expansion requires a macro defined later in the library to produce a definition form).

Macro expansion obeys lexical scope in the sense that if a macro expansion introduces a identifier that is bound in the context of the macro definition, it refers to the binding in the environment of the macro-definition library, not in the environment of the expansion.

An error is signalled if a macro expands to an <impexp-form>; all imports and exports must be apparent before macro expansion, and they must appear before any <comdef-form>. A indirect-export form, in contrast, can appear in a macro expansion.

To avoid ambiguity, no local identifier can be imported or defined multiple times.

Library Semantics

Since a library contains a sequence of definitions and expressions, a library can be used as an application. Alternately, a Scheme program standard (not part of this SRFI) may refer to libraries to be executed as part of the program. In either case, executing the library must imply executing any library that it imports.

More formally, we define library execution in terms of invoking libraries. To execute a library, invoke it at phase 0.

To invoke a library at phase N:

The order in which imported libraries are invoked is not defined, but imported libraries must be invoked before the library's definitions and top-level expressions are evaluated.

As noted in Library Macro Expansion, compiling a library implies visiting some libraries that it imports, as well as invoking others. Specifically, compiling a library requires the same action as visiting the library at phase 0.

To visit a library at phase N:

The order in which imported libraries are visited and invoked is not defined, but imported libraries must be visited and invoked before the library's syntax definitions are evaluated.

An error is signalled if an identifier is used out of its declared phase(s).

Library References

A library refers to another library through a <lib-path>. Conceptually, a <lib-path> is a name within a universal space. Library names in the universe are arranged in a tree, so that a <lib-path> can be relative, much like a filesystem path or URL.

A <lib-path> is therefore represented by a hierarchical URI string (see RFC 2396) or a symbol shortcut (defined below). The URI scheme ``scheme'' should be used to refer to libraries that are released by the Scheme community at large, in much the same way that a Java package name is used. For example, "scheme://acme.com/wiley/quicksort" might refer to a quicksort library that is distributed by Wiley at Acme. If Wiley's quicksort library contains the <lib-path> "utils", it can be expanded as "scheme://acme.com/wiley/utils".

The URI "scheme://r6rs" must be supported by every implementation, and it must export all bindings in R5RS, plus import, export, and indirect-export. URIs starting with "scheme://rNrs" and "scheme://srfi-N" are reserved for definition by standards processes.

The way that URIs for library references are mapped to library declarations is implementation-specific. Implementations might, for example, interpret a ``http'' URI as a reference to library within a UTF-8-encoded package on the web. The resolution of a relative <lib-path> for a library name (as in the examples of Library Form Syntax) is also implementation-dependent, but by convention, sequences of declarations with relative names (again, as in ) are all relative to the same (unspecified) absolute name.

Eval, Load, and the Read-Eval-Print Loop

This SRFI defines no interaction between library and the eval and load procedures. Programmers should not expect that an eval or load that appears with a library body either references or extends the definitions within the library.

This SRFI also does not require that implementations support a mode to evaluate library forms interactively. An implementation's native module form might be completely different from library, and the implementation may simply supply a tool to translate packaged libraries into the implementation's native format.

Reference Implementation

A reference implementation for PLT Scheme 299.200 supports library forms in the MzScheme REPL, and also a way to install a set of library declarations into the usual PLT Scheme module space (by splitting the library sequence into separate files, adding #reader(lib "library.ss" "r6rs") to the top of each file).

Acknowledgments

This SRFI was written in consultation with the full set of R6RS editors: Will Clinger, Kent Dybvig, Marc Feeley, Matthew Flatt, Manuel Serrano, Michael Sperber, and Anton van Straaten.

References

See www.readscheme.org for an extensive list of references to work on modules. The following are the most immediate influences on this SRFI:

Copyright

Copyright (C) Matthew Flatt and Kent Dybvig (2005). All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: David Van Horn