This SRFI is currently in withdrawn status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-83 @nospamsrfi.schemers.org
. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
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.
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.
The import
and export
forms could
be allowed in macro expansions. In that case, the exports and imports
of a module can be determined only by macro-expanding the
module.
Better support for records may be useful, possibly through a
co-export
form:
(co-export <co-spec>*)
, where
<co-spec>
contains an identifier defined or
imported into a library followed by a list of other identifiers
defined or imported into the library:
<co-spec> = (<identifier> <identifier>*)
The meaning of a <co-spec>
is that if the
first identifier becomes exported, either through
export
or co-export
, then the
remaining identifiers are also exported. Similarly, if the
first identifier is indirectly exported through
indirect-export
or co-export
, then
then remaining identifiers are indirectly exported.
To be useful for managing records, probably
co-export
must be allowed in macro expansions. In
that case, the full exports of a module can be determined only
by macro-expanding the module.
Instead of having the programmer declare import phases with
(for .... expand)
, etc., phases could be inferred
from uses of the imported identifiers. In that case,
determining the phase of an import would require macro-expanding
the module body.
The specification for library references should probably
include versioning. Without any change to the SRFI, we could say
that a version is part of a library's name in the universal
namespace, in which case every library reference selects a
specific version. A better system would allow specifications
such as "major version N, minor version of M or later", etc. In
this case, a <lib-path>
must be resolved to a
specific library name (including the version) based on the
available versions.
This standard addresses the following specific goals:
It does not address the following:
5
by itself as a
program).
A library declaration contains the following elements:
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.
<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:
only
form produces a subset of the bindings
from another <X-set>
, including only
the listed internal (external) <identifier>
s;
if any of the included <identifier>
s is not
in <X-set>
, an error is reported.
except
form produces a subset of the bindings
from another <X-set>
, including all but
the listed internal (external) <identifier>
s;
if any of the excluded <identifier>
s is not
in <X-set>
, an error is reported.
add-prefix
adds a prefix to each internal
(external) name from another <X-set>
.
rename
form, for each pair of identifiers
(<identifier> <identifier>)
, removes a binding from the set from
<X-set>
, and adds it back with a
different internal (external) name. The first
identifier
is the original internal (external)
name, and the second identifier
is the new
internal (external) name. If the original name is not in
<X-set>
, or if the new name is
already in <X-set>
, an error is
reported.
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 ...)]))) |
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.
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:
for .... expand
, and that is not yet invoked at phase
N+1.
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).
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.
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.
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).
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.
See www.readscheme.org for an extensive list of references to work on modules. The following are the most immediate influences on this SRFI:
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.