
Modules in Jscheme
originally implemented by Derek Upham (derek.upham at ontain.com)modified by Tim Hickey (5/04) to use the "use-module" syntax
Modules are implemented using the facilty for creating and manipulating multiple independent JScheme instances. Each such instance constains a DynamicEnvironment which is a set of bindings of symbols to values.
Any scheme file can be loaded as a module. The key idea is that when a file is loaded as a module it is first loaded into its own Jscheme instance (with a new DynamicEnvironment). Then the original environment is extended by importing some or all of the bindings from the module (possibly with a prefix).
The environments created when loading a module are cached so that modules are only loaded once. Moreover, a module can recursively use submodules, but modules can not depend on each other recursively as this will cause an infinite loop.
The syntax for using modules is as follows:
- MODULE is a either a filename, or a URL, or the name of a compiled Scheme class
- SPECIFIER is one of the following:
- 'import-procedures
- 'import-macros
- 'import
- SYMBOLS is either the symbol 'all or is a list of symbols. The default value is 'all In the former case, all symbols defined in the module are imported to the current environment; in the latter case, only those symbols in the list are imported
- PREFIX is a string which is prepended to each imported symbol from the module before that value is put into the current environment. The default value is the empty string "".
Examples
The following are all equivalent:The following examples show how to get more control over what is imported. You can import only procedures, or only macros, or import some subset of the procedure or macros.
Explanations
(use-module "elf/basic.scm")
is equivalent to
(use-module "elf/basic.scm" 'import 'all "")
This creates a new JScheme instance js and loads elf/basic.scm into js. Then it takes each of the symbols defined in the js environment and transfers those bindings verbatim to the current environment. Note: any closures imported into the current environment will point to the js environment. Hence, we can freely modify the values of the primitives (set! car cdr) without changing the behavior of the procedures and macros imported from elf/basic.scm.(use-module "elf/basic.scm" 'import-procedures)
is equivalent to
(use-module "elf/basic.scm" 'import-procedures 'all "")
This does the same as above, but only imports the procedures, not the macros.(use-module "elf/basic.scm" 'import-procedures '(describe apropos))
is equivalent to
(use-module "elf/basic.scm" 'import-procedures '(describe apropos) "")
This does the same as the first example, but only imports two things, the bindings for thedescribe
andapropos
procedures.(use-module "elf/basic.scm" 'import-macros '(dotimes))
is equivalent to
(use-module "elf/basic.scm" 'import-macros '(dotimes) "")
This does the same as the first example, but only imports the binding for thedotimes
macro.(use-module "elf/basic.scm" 'import '(describe apropos dotimes))
This does the same as the first example, but only imports three things, the bindings for thedescribe
andapropos
procedures and the binding for thedotimes
macro.(use-module "elf/basic.scm" 'import-procedures '(describe apropos) "elf:")
This does the same as the previous example, but it binds thedescribe,apropos
values from the js environment to the symbols:elf:describe elf:apropos Note that the prefix is not applied to macros. The rationale here is that macros are changing the syntax of the language, and that introducing prefixes makes the syntax even more confusing.(use-module "elf/basic.scm" 'import-procedures '(describe apropos) "elf-")
This does the same as the previous example, but it binds thedescribe,apropos
values from the js environment to the symbols:elf-describe elf-apropos Note that the prefix is not applied to macros. The rationale here is that macros are changing the syntax of the language, and that introducing prefixes makes the syntax even more confusing.(use-module "elf/basic.scm" 'import '(describe apropos dotimes) "elf:")
This does the same as the previous example, but it binds thedescribe,apropos,dotimes
values from the js environment to the symbols:elf:describe elf:apropos dotimes Note that the prefix is not applied to macros. The rationale here is that macros are changing the syntax of the language, and that introducing prefixes makes the syntax even more confusing.
Deprecated approach to Modules
using environment-import and language-import
Modules are loaded using one of the following procedures:
(environment-import FILENAME PREFIX) (environment-import FILENAME) (language-import FILENAME)Each of these creates a new environment (containing a copy of the (initial-environment)) and loads the specified file into that environment. After the load, the environment is "locked" so that one cannot change any bindings in the environment. Next, the variables in the environment are imported into the current environment and are bound to the values they have in the original environment, as follows:
- For language-import only the variables bound to macros are imported.
- For the environment-import only those variables not bound to macros are imported.
Examples
> (environment-import "elf/basic.scm" "foo:") #t > (foo:map* (lambda (x) (* x 2)) #(1 2 3)) (2 4 6) > (map* (lambda (x) (* x 2)) #(1 2 3)) (map* {jsint.Closure ??[1] (x)} #(1 2 3) ) ==================================== SchemeException:[[ERROR: undefined variable "map*"""]]Note that "map*" is renamed to "foo:map*" for us, but internally it still uses the plain "iterate" binding that is always does. You can also load classes that use the normal "load()" method initialization convention:
> (environment-import jlib.JLIB.class "jl:") #t > (jl:menu "m1" (jl:menuitem "foo")) java.awt.Menu[menu0,label=m1,tearOff=false,isHelpMenu=false]If we pass #f for the prefix, nothing is prefixed; the function acts like "load". Assigning prefixes at load time gives more flexibility than depending on hierarchical module trees (c.f. namespace aliases in C++). Note that generic functions are merged correctly, not treated like other variables.
This mechanism only works for libraries that are idempotent; it can't matter how many times you've loaded the library, or which of the times you've loaded it you're referring to. But a large class of Scheme code satisfies this restriction. (Locking-down the new environments catches at least some violations of this rule.)
We have not yet added "caching" to the module loading code. Each time an environment-import or language-import is encountered, a new environment is created. This makes it easier to debug multi-module programs.
Each time a prefixed environment is imported, a prefixed copy of the initial environment is also imported. Thus
(environment-import "file.scm" "a:")will add the non-macro definitions in file.scm as well as all of the primitives "a:car" "a:cdr" ... etc. If this file also imports a file with prefix say "b:", then variables of the form "a:b:car" "a:b:cdr", ... will be imported into the interaction environment.
eval
and environments
There are three named environments accessible from Jscheme:
(null-environment) -- which contains no definitions, but does handle Javadot notation (initial-environment) -- which contains only the Jscheme primitives (interaction-environment) -- which is the "current" toplevel environmentThe
eval
procedure can be called with any of these three
environments as its second parameter, e.g.
> (eval `(jsint.Op.add 3 4) (null-environment)) 7 > (eval '(begin (set! + *) (+ 3 4)) (initial-environment)) SchemeException:[[ERROR: attempting to alter bindings in locked environment:+ <-- {jsint.Primitive *[0,n]}""]] > (eval '(begin (set! + *) (+ 3 4)) (interaction-environment)) 12 > (+ 3 4) 12