I have used this technology only in the context of a read-eval-print-loop (REPL).
I am unhappy with the implemented and proposed schemes for Scheme modules which are conceived to package Scheme functionality for use by other functions. Other module schemes of which I am aware add to the language. This module scheme uses the language as is. We agree that including all the code all the time is a bad idea. We do not agree on much else.
It is good to have a list of desiderata before you begin design. I didn’t. In retrospect, however, here are some:
I rely here only on standard Scheme 5 library functions to provide a minimal module system and a specific Scheme repository. I have tested this pretty well with MzScheme 372. The notion is that a module is merely a file in this repository. That file contains one Scheme expression. The Scheme expression (fileVal fn) returns the value of the sole expression in the file fn in this here repository. (Actually the first expression in the file.) The expression in the file is evaluated in the standard Scheme 5 environment extended by identifiers ylppa and fileVal bound to the same function as already described. Evaluating (fileVal fn) twice with the same file name skips re-reading the file the 2nd time and returns the value produced the first time. Said otherwise (eq? (fileVal string) (fileVal string)) yields true whereas (eq? (cons 0 0) (cons 0 0)) yields false. The first expression yields true even if the contents of the file is (cons 0 0). fileVal is defined here. See this about using on your computer. The file names in the repository serve as a module name space. The defining occurrences of module names are the file names in the directory of the repository and the applied occurrences are quoted strings as arguments to fileVal.
I couldn’t think of anything simpler and this seems good enough.
I am happy with this style, after adapting about 80 mostly small programs to the repository.
This plan has much in common with E-makers.
If the contents of file Cons is (cons 0 0) then the execution of (set-car! (fileVal "Cons") 2) will change the meaning of a subsequent (car (fileVal "Cons")) in any environment. The current content of the repository produces no such mutable values.
On the other hand if code in file zot executes (set! car cdr) then (car '(2 . 3)) => yields 2 even after performing (fileVal "zot") thanks to the fact that the file code runs in a different environ. As the repository stands now however executing (set-car! (fileVal "Matrix")) ruins the subsequent meaning of (car (fileVal "Matrix")) in all other environments.
I think it would be easy to write a shallow mutable copier which would return a new mutable value to each invocation of fileVal and thus shut off such interference between users. I know no automatic test that repository members do not return one mutable value to different callers. A run-time enforcement would be expensive.
The semantics of fileVal can be approximately defined by replacing each occurrence of (fileVal name) by the first Scheme expression in the repository file named name. A difference is that this would allow free variables within repository members to be resolved in the top level name space, a construction that is not possible with fileVal.
I think that lambda abstraction is so powerful that macros are superfluous. I might be convinced by reading some programs that claim to be improved by macros.
In the context of JavaScript, at least, it is interesting to imagine secure hashes of sources as module names. Perhaps the browser conventionally maintains a cache of JS values indexed by the SHA of their sources, and consults the home site of the referencing JavaScript code upon cache miss. This plan might require that such JS values be transitively immutable. I am not sure whether this can be enforced mechanically.
Evidently the language E has such a scheme.
The above repository scheme does not address issues of authority outside the language platform proper but it serves as a good place to start.
Deleting all my files is an example of what I will call exo-authority here.
A module cannot access the dangerous Scheme primitive call-with-output-file, which conveys a great deal of exo-authority, without explicitly including that symbol.
That is unless the platform has included the eval variation of one argument, left over from earlier Schemes.
(eval (list (string->symbol (string-append "call-with" "-output-file")) "target-file-name" '(lambda (p) (write "crud" p))))Clobbers a file in my directory without including the name, call-with-output-file, of the Scheme primitive with the dangerous exo-authority. R5RS does not include this variation on eval but many R5RS platforms include it. The program fileVal could be modified to bind eval to a tamed function that accepted only two arguments. scheme-report-environment as provided in MzScheme must also be tamed as it carries the same dangerous weapons. This illustrates a strength in the Scheme semantics and a weakness in choice of standard library functions.
(include "header.scm")in your source is replaced by
(begin file content)This gives the author of the file access to your mutable environment.
(begin (define car cdr))as file content will ruin most programs.