Overriding imports

3 views
Skip to first unread message

Marc Nieper-Wißkirchen

unread,
Mar 13, 2022, 6:14:33 AMMar 13
to scheme-re...@googlegroups.com
Consider the libraries

(define-library (foo)
(export hello-world)
(import (scheme base))
(begin
(define (hello-world)
(display "Hello world!)
(newline))))

(define-library (foo*)
(export hello-world)
(import (scheme base))
(begin
(define hello-world
(case-lambda
(() (hello-world "world"))
((user)
(display "Hello ") (display user) (display "!")
(newline))))))

Although hello-world exported by (foo*) is (barring error conditions)
backward compatible with hello-world exported by (foo), importing
(foo) and (foo*) into the same environment is an error.

I would like to suggest a mechanism that allows a controlled form of
overriding and that does not suffer from inadequacies found with
previous approaches. Such a flawed previous approach is Guile's
replace feature, for example. Assume that (foo) also exports a macro
that uses hello-world as auxiliary syntax. This wouldn't work in the
importing environment anymore if the binding of hello-world is
silently replaced.

So any viable solution must not replace the binding of hello-world. In
fact, hello-world exported by (foo) and (foo*) both must have the same
binding! For that, the implementations of (foo) and (foo*) have to be
able to communicate (which is actually a good requirement).

I propose the following forms:

(define-version-identifier <version-id>)
(define-versioned-identifier <id> <version-id> ...)
(define-version <id> <version-id> <defining-id>)

The form (define-versioned-identifier <id> <version-id> ...) defines
an identifier that has an associated dictionary of versions attached
to it. The keys of this dictionary are the bindings of the identifiers
<version-id> ... Initially, the dictionary is empty. The form
(define-version <id> <version-id> <defining-id>) adds a key/value pair
to the dictionary associated with <id>. The key is the binding of
<version-id>, the value is the identifier <defining-id>. When <id> is
later expanded, the key that appears latest in the list of
<version-id>s is looked up and <id> replaced by the associated
<defining-id>.

The above example could be rewritten as:

(define-library (foo-common)
(export foo-simple foo-complex hello-world)
(import (scheme base) (versioned-identifier))
(begin
(define-version-identifier foo-simple)
(define-version-identifier foo-complex)
(define-versioned-identifier hello-world foo-simple foo-complex)))

(define-library (foo)
(export hello-world)
(import (scheme base) (foo-common) (versioned-identifier))
(begin
(define (simple-hello-world)
(display "Hello world!)
(newline))
(define-version hello-world foo-simple simple-hello-world)))

(define-library (foo*)
(export hello-world)
(import (scheme base) (foo-common) (versioned-identifier))
(begin
(define complex-hello-world
(case-lambda
(() (hello-world "world"))
((user)
(display "Hello ") (display user) (display "!")
(newline))))
(define-version hello-world foo-complex complex-hello-world))))

The proposal should be implementable with SRFI 213 (part of R7RS
large). I'm happy to hear better proposals for the names. :) The
"define-" prefix is correct, though, because bindings are created by
each form.

It can be used by (SRFI 219) as part of R7RS large, for example, so
that importing both (srfi 219) and (scheme base) does not produce an
error.

It is also crucial for an attempt to unify R6RS and R7RS (small)
within R7RS Large. For example, (rnrs) would export a version of the
map procedure that raises an assertion violation if the list arguments
don't have the same length each, while the version in (scheme base)
would short-circuit. (Both versions have their merits.) In order to
make (import (rnrs) (scheme base)) work, the (barring error
conditions) more general (scheme base) version would override the
(rnrs) version using the technique shown above.
Reply all
Reply to author
Forward
0 new messages