From: Derek U. <Der...@on...> - 2002-09-30 22:27:23
|
This weekend I got a simple module system up and running. Simple, in this case, means that it is compatible with the "module:symbol" naming convention used in a lot of Scheme libraries. The steps were as follows: 1. Symbols no longer contain their values. There are separate first-class dynamic environments that contain the values. 2. Free variables are no longer Symbols after code analysis; they are DynamicVariable objects, bound to a particular Symbol and DynamicEnvironment. This means that free variables are always looked up in the environment they were defined in. 3. A new function "loading-environment" acts as "load", but all definitions are done in a new environment with ``standard'' initial bindings. The new environment is returned. You can pass that environment as the second parameter to "eval", e.g., (let ((e (loading-environment "elf/basic.scm"))) (eval 'iterate e)) The new environment is locked-down; you can't change any of its dynamic bindings. 4. A new builtin (non-exposed) function "importBindings" runs through all the bindings in an environment and adds similar bindings to the current environment. The new bindings can either be identical, or they can have a prefix attached. 5. A new function "environment-import" does both the loading and the importing in succession. The upshot is that you can to the following: > (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.) The big chunks seem to be complete; i'm now looking for corner cases that might hide bugs. Is this new functionality worth finishing up? Derek -- Derek Upham Senior Software Engineer Ontain 1750 112th Ave NE, Suite C-245 Bellevue, WA 98004-3727 Tel: 425-460-1886 der...@on... |