>> The default environment is irrelevant to most Scheme libraries, since > it is irrelevant to any library defining modules with > ‘define-module’, ‘define-library’ or ‘library’ forms (which is pretty > much the only reasonable way to define modules unless you are making > your own system). This already reduces a lot of compatibility > concerns. An important exception is anyone doing ‘eval’ or compiling > expressions (think anyone doing the equivalent of “guile -l > script.scm”. > >Unfortunately, the "default environment" in the sense of "core bindings" definitely is relevant to libraries using `define-module`. For example, in this module: ``` (define-module (foo) #:export (foo)) (define foo (cons 'a 1)) ``` at least the bindings for `define`, `cons`, and `quote` are implicitly imported from `(guile)`. Yes, that’s why #:pure? #true (IIRC the right keyword argument) should be used. (+ deprecation warning when #:pure? is absent?) >R6RS libraries don't have this problem. If instead you write: ``` #!r6rs (library (foo) (export foo) (import (rnrs base)) (define foo (cons 'a 1))) ``` Here you are demonstrating how R6RS libraries have the _same_ problem. You should at least have included a version number next to (rnrs base). Who knows, maybe R8RS will rename ‘cons’ to ‘make-pair’? As written, it is implicit which version of (rnrs base) is imported (*) – is it R6RS? Is it R7RS (no, because R7RS uses (scheme ...), but it _could_ have used (rnrs ...) instead)? A hypothetical future R8RS? (There might be (?) a rule that if no version is mentioned, the latest version available is used, but that’s its own source of incompatibilities ...) (*) no #!r6rs does not count – that’s good for lexical syntax, but the version number of the _modules_ go into the (import ...). AFAICT, nowhere is the version number of the (rnrs ...) treated specially w.r.t. #!r6rs. Compare this with how (implicitly) (guile) is imported – Guile doesn’t know _which_ version of the (guile) API should be used because it isn’t told (and currently, there is no option to tell Guile). That a module is implicitly imported isn’t really a problem, what is the problem is that the _version_ (in terms of API, not implementation) is implicit. > the bindings for `define`, `cons`, and `quote` are explicitly imported from `(rnrs base)`. If the `import` clause were empty, you would get unbound identifier errors. (The report specifies the meaning of `library` and its `export` and `import` sub-forms, but it also specifies that they are not bound by any of the libraries specified by the report, though `library` is often implemented as a binding in some sort of implementation-specific start-up environment that is not in scope inside a library.) >Similarly, in the Racket module: ``` (module foo racket/base (provide foo) (define foo (cons 'a 1))) ``` >the module's language, `racket/base`, is explicitly the source of the binding for `provide` as well as those for `define`, `cons`, and `quote`. If you replaced `racket/base` with `typed/racket/base` or `lazy`, you would get a valid module with different meanings for those bindings. I could be wrong since I don’t the well how Racket is developed, but barring evidence to the contrary, I’d assume that the bindings in racket/base vary depending on the version of Racket – just like Guile’s default environment. Assuming this is true, then Racket has (in terms of implicitness) pretty much (not exactly, but _pretty much_) the same problem as Guile. >Of course, you could avoid some indentation by writing the above as: ``` #lang racket/base (provide foo) (define foo (cons 'a 1)) ``` Unless the API of racket/base never changes over time: same problem, no version number or equivalent is included. >Andy Wingo's "lessons learned from guile, the ancient & spry" () concludes in part: > But as far as next steps in language evolution, I think in the short > term they are essentially to further enable change while further > sedimenting good practices into Guile. On the change side, we need > parallel installability for entire languages. Racket did a great job > facilitating this with #lang and we should just adopt that. > >I agree. > > Obviously `#lang` does much more than this (we Racketeers tend to say `#lang` as shorthand for a bunch of complementary but mostly independent features), but I think a particularly important aspect is that each module should explicitly specify its "language"/"default environment"/"core bindings". If done well, this actually *avoids* the compatibility concerns some have raised: when you want to make a breaking change, you pick a new name, and things using the old name keep working, interoperably. The thing is, AFAICT using #lang does not imply “explicitly specifying the core bindings”, as mentioned previously – AFAIK Racket does not pick a new name every time the list of bindings is changed in some way. There are benefits to have a standard way to indicate the language of a file (e.g. #lang) (and I guess if you want to you could define a bunch of fake languages that also include a couple of imported modules(and which these are depend on the precise fake language), though I don’t see why not just import those as modules), but that doesn’t mean we should repeat Racket’s mistakes with what else (and how) it also uses #lang for. Best regards, Maxime Devos