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: (use-module MODULE) (use-module MODULE SPECIFIER) (use-module MODULE SPECIFIER SYMBOLS) (use-module MODULE SPECIFIER SYMBOLS PREFIX) where

Examples

The following are all equivalent: (use-module "elf/basic.scm") (use-module "elf/basic.scm" 'import) (use-module "elf/basic.scm" 'import 'all) (use-module "elf/basic.scm" 'import 'all "")
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. > (use-module "elf/basic.scm" 'import-procedures 'all) > (print "string") > (use-module "elf/basic.scm" 'import-procedures '(describe print)) > (describe print) > (use-module "elf/basic.scm" 'import-macros 'all) > (dotimes (x 5) (print x)) > (use-module "elf/basic.scm" 'import-macros '(dotimes)) > (dotimes (x 5) (display x)) >(use-module "elf/basic.scm" 'import '(describe print dotimes)) > (dotimes (x 5) (print x)) Finally, you can add arbitrary prefixes to the symbols that are imported, but prefixes are never applied to the macros. > (use-module "elf/basic.scm" 'import-procedures '(describe print) "elf:") > (elf:describe 5) > (use-module "elf/basic.scm" 'import-procedures '(describe print) "elf-") > (elf-describe (java.awt.Button. "hi")) > (use-module "elf/basic.scm" 'import '(describe print dotimes) "elf:") > (dotimes (x 5) (elf:print x))


Explanations

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: The environment-import allows you to specify a prefix string which will be prepended to all variables before they are imported into the current environment.

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 environment
The 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