guile-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

RE: The Guile junk drawer and a C plea


From: Maxime Devos
Subject: RE: The Guile junk drawer and a C plea
Date: Sun, 30 Jun 2024 00:41:12 +0200

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

[Prev in Thread] Current Thread [Next in Thread]