>> 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"
(<https://wingolog.org/archives/2020/02/07/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

Reply via email to