The design of the library system was a challenging process: Many existing Scheme implementations offer “module systems”, but they differ dramatically both in functionality and in the goals they address. The library system was designed with the primary requirement of allowing programmers to write, distribute, and evolve portable code. A secondary requirement was to be able to separately compile libraries in the sense that compiling a library requires only having compiled its dependencies. This entailed the following corollary requirements:
Composing libraries requires management of dependencies.
Libraries from different sources may have name conflicts. Consequently, name-space management is needed.
Macro definitions appear in portable code, requiring that macro bindings may be exported from libraries, with all the consequences dictated by the referential-transparency property of hygienic macros.
The library system does not address the following goals, which were considered during the design process:
mutually dependent libraries
separation of library interface from library implementation
local modules and local imports
This section discusses some aspects of the design of the library system that have been controversial.
A library definition is a single form, rather than a sequence of forms where some forms are some kind of header and the remaining forms contain the actual code. It is not clear that a sequence of forms is more convenient than a single form for processing and generation. Both syntactic choices have technical merits and drawbacks. The single-form syntax chosen for R6RS has the advantage of being self-delimiting.
A difference between top-level programs and libraries is that a program contains only one top-level program but multiple libraries. Thus, delimiting the text for a library body will be a common need (in streams of various kinds) that it is worth standardizing the delimiters; parentheses are the obvious choice.
Some Scheme implementations feature module systems that allow a module’s bindings to be imported into a local environment. While local imports can be used to limit the scope of an import and thus lead to more modular code and less need for the prefixing and renaming of imports, the existence of local imports would mean that the set of libraries upon which a library depends cannot be approximated as precisely from the library header. (The precise set of libraries used cannot be determined even in the absense of local import, because a library might be listed but its exports not used, and a library not listed might still be imported at run time through the environment procedure.) Leaving out local import for now does not preclude it from being added later.
Some Scheme implementations feature local libraries and/or modules, e.g., libraries or modules that appear within top-level libraries or within local bodies. This feature allows libraries and top-level programs to be further subdivided into modular subcomponents, but it also complicates the scoping rules of the language. Whereas the library system allows bindings to be transported only from one library top level to another, local modules allow bindings to be transported from one local scope to another, which complicates the rules for determining where identifiers are bound. Leaving out local libraries and modules for now does not preclude them from being added later.
The import and export clauses of the library form are a fixed part of the library syntax. This eliminates the need to specify in what language or language version the clauses are written and simplifies the process of approximating the set of libraries upon which a library depends, as described in section 7.2. A downside is that import and export clauses cannot be abstracted, i.e., cannot be the products of macro calls.
Opinions vary on how libraries should be instantiated and initialized during the expansion and execution of library bodies, whether library instances should be distinguished across phases, and whether levels should be declared so that they constrain identifier uses to particular phases. This report therefore leaves considerable latitude to implementations, while attempting to provide enough guarantees to make portable libraries feasible.
Note that, if each right-hand side of the keyword definition and keyword binding forms appearing in a program is a syntax-rules or identifier-syntax form, syntax-rules and identifier-syntax forms do not appear in any other contexts, and no import form employs for to override the default import phases, then the program does not depend on whether instances are distinguished across phases, and the phase of an identifier’s use cannot be inconsistent with the identifier’s level. Moreover, the phase of an identifier’s use is never inconsistent with the identifier’s level if the implementation uses an implicit phasing model in which references are allowed at any phase regardless of any phase declarations.
The asymmetry in the prohibitions against assignments to explicitly and implicitly exported variables reflects the fact that the violation can be determined for implicitly exported variables only when the importing library is expanded.
Library names are compound. This differs from the treatment of identifiers in the rest of the language. Using compound names reflects experience across programming languages that a structured top-level name space is necessary to avoid collisions. Embedding a hierarchy within a single string or symbol is certainly possible. However, in Scheme, list data is the natural means for representing hierarchical structure, rather than encoding it in a string or symbol. The hierarchical structure makes it easy to formulate policies for choosing unique names or possible storage formats in a file system. See appendix on “Unique library names”. Consequently, despite the syntactic complexity of compound names, and despite the potential mishandling of the hierarchy by implementations, the editors chose the list representation.
Libraries and import clauses optionally carry versioning information. This allows reflecting the development history of a library, but also significantly increases the complexity of the library system. Experience with module systems gathered in other languages as well as with shared libraries at the operating-system level consistently indicates that relying only on the name of a module for identification causes conflicts impossible to rectify in the absence of versioning information, and thus diminishes the opportunities for sharing code. Therefore, versioning is part of the library system.
Implementations are encouraged to prohibit two libraries with the same name but different versions to coexist within the same program. While this prevents the combination of libraries and programs that require different versions of the same library, it eliminates the potential for having multiple copies of a library’s state, thus avoiding problems experienced with other shared-library mechanisms, including Windows DLLs and Unix shared objects.