bug#48425: Should #nil be equal? to '()?

2021-06-24 Thread Leo Prikler
Hi Taylan,

after a lengthy discussion in IRC, you invited me to reply to this bug
report, so that's what I'm doing.  I will make short references to that
discussion, but I won't rehash all of it.  Anyone who is interested in
reading it in all detail, should inspect the public logs:
  http://logs.guix.gnu.org/guile/2021-06-24.log#170309

Am Freitag, den 14.05.2021, 21:36 +0200 schrieb Taylan Kammer:
> I believe it would be better if #nil were equal? to ().
> 
> It would keep *not* being equal? to #f and as such not disturb the
> property of transitiveness.
I don't think there's a good reason to prefer one of this equal?ity
over the other – it all comes down to personal preference if at all.

> Making #nil and () be equal? would be a lot more intuitive since
> they both represent the empty list, and since equal? is commonly
> used to test the equality of lists.  Meeting this expectation would
> probably prevent a common type of unexpected behavior where a list
> coming from Elisp code is not equal? to a list coming from Scheme
> code, even though they have the same contents.
I don't think, that this is a good reason to make them equal?  We've
had our lengthy discussion on the philosophy of equality, which I'm
going to cut short here, as that's not the point you're making, but for
the protocol's sake, #nil and '() are not philosophically equal?

Regarding intuitiveness, I think this actually does more harm than
good.  I could argue that #nil be equal? to #f on the grounds of
intuitiveness, but more importantly

(if (equal? a b) (equal? (if a #t #f) (if b #t #f)))

should only ever return #t or *unspecified*, never #f (insert '() and
#nil as a and b).

Finally on the point of making equal?ity work that way for the sake of
interoperability with other Lisp dialects such as Emacs Lisp or
Clojure, this assumes that this style of comparison through equal? is
"good Scheme", when I'd argue, that it is in fact not.  Pattern
matching and dedicated comparison operators are much saner alternatives
most of the time.

There are multiple ways of getting around the issue of #nil, '() and #f
not being equal? to each other.  The first would be to define an equal-
modulo-nil? procedure, which returns #t if both arguments are nil? 
Another, that would implement your style, would be the following:

--8<---cut here---start->8---
;; Don't forget to test this when actually using it
(define my-equal?
  (match-lambda*
((() ()) #t) ;; match '() against #nil
(((a . as) (b . bs))
 (and (my-equal? a b) ;; recurse into the first sub-element 
  (list= my-equal? as bs))) ;; recurse into the others
((a b) (equal? a b ;; fall back to base equal?
--8<---cut here---end--->8---

Of course, you can also take the equal? you wrote for this patch and
include it in your own library, but be sure to document it so as to not
confuse the readers of your code :)

Note, that any such implementations will come with certain limitations,
but I suppose that's what you'd want out of them.

> Attached is a patch to realize the change.  Note that it increases
> the size of compiled code that uses equal?.  I don't know if this
> represents a problem or not.
I personally don't think bytecode optimizations are something to
consider if one can achieve correctness on the other hand, but this
solution has the downside of being both wrong (philosophically, as
discussed above and in IRC) and heavier to compute.  That's not
something a standard library should aim for :P

Regards,
Leo






bug#47875: Elisp reader doesn't handle keywords

2021-06-05 Thread Leo Prikler
Am Samstag, den 05.06.2021, 18:51 +0300 schrieb Ivan Sokolov:
> In Scheme keywords and symbols are separate entities, but in Elisp
> keywords are just self-quoted symbols. I think your solution will
> cause problems with Elisp algorithms that expect keywords to be
> symbolp.
You could define Elisp symbolp as 
(lambda (thing)
  (or (funcall (@ (guile) symbol?) thing)
  (funcall (@ (guile) keyword?) thing)))
but perhaps there's an even faster way to check this property.
The other way round – using Emacs keywords as Scheme symbols – will not
perform well if you think about Emacs code calling a Scheme function
that expects keywords.

Regards,
Leo






bug#47875: Elisp reader doesn't handle keywords

2021-04-18 Thread Leo Prikler
Hi Guilers,

Using Guile 3.0.5, the elisp reader does not seem to support keywords
written in Elisp's preferred prefix-style notation (:keyword).

scheme@(guile-user)> ,L elisp
Happy hacking with Emacs Lisp!  To switch back, type `,L scheme'.
elisp@(guile-user)> :keyword
ice-9/boot-9.scm:1669:16: In procedure raise-exception:
Unbound variable: :keyword

Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
elisp@(guile-user) [1]> ,q
elisp@(guile-user)> (funcall (@ (guile) symbol->keyword) 'keyword)
$1 = #:keyword

Meanwhile in Emacs M-: :keyword yields :keyword.  Using Scheme-style
keywords also does not work:

elisp@(guile-user)> #:keyword
While compiling expression:
uninterned symbol cannot be saved to object file #

I think Guile should produce keyword objects for the former, so that we
don't need to talk about the latter.

Regards,
Leo






bug#45131: [PATCH] Compile directly to target language if no joint is found.

2020-12-29 Thread Leo Prikler
This enables the compilation from "manually" written Tree-IL to
bytecode.  See also .

* system/base/compile.scm (read-and-compile)[(joint #f)]:
Join exps using the default joiner for to.
: Compute compiler for to.
* test-suite/test/compiler.test ("read-and-compile tree-il"): New test.
---
 module/system/base/compile.scm | 26 +++---
 test-suite/tests/compiler.test | 22 ++
 2 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/module/system/base/compile.scm b/module/system/base/compile.scm
index 567765dc0..41ad0158a 100644
--- a/module/system/base/compile.scm
+++ b/module/system/base/compile.scm
@@ -310,16 +310,20 @@
 (match (read-and-parse (current-language) port cenv)
   ((? eof-object?)
(close-port port)
-   (compile ((or (language-joiner joint)
- (default-language-joiner joint))
- (reverse exps)
- env)
-#:from joint #:to to
-;; env can be false if no expressions were read.
-#:env (or env (default-environment joint))
-#:optimization-level optimization-level
-#:warning-level warning-level
-#:opts opts))
+   (if joint
+   (compile ((or (language-joiner joint)
+ (default-language-joiner joint))
+ (reverse exps)
+ env)
+#:from joint #:to to
+;; env can be false if no expressions were read.
+#:env (or env (default-environment joint))
+#:optimization-level optimization-level
+#:warning-level warning-level
+#:opts opts)
+   ((default-language-joiner to)
+(reverse exps)
+env)))
   (exp
(let with-compiler ((from from) (compile1 compile1))
  (cond
@@ -332,7 +336,7 @@
(let ((from (current-language)))
  (with-compiler
   from
-  (compute-compiler from joint optimization-level
+  (compute-compiler from (or joint to) optimization-level
 warning-level opts
 
 (define* (compile x #:key
diff --git a/test-suite/tests/compiler.test b/test-suite/tests/compiler.test
index dc75d0ac7..cdc26c751 100644
--- a/test-suite/tests/compiler.test
+++ b/test-suite/tests/compiler.test
@@ -337,3 +337,25 @@
   (pass-if-equal "test terminates without error" 42
 (test-proc)))
 
+(with-test-prefix "read-and-compile tree-il"
+  (let ((code
+ "\
+(seq
+  (define forty-two
+(lambda ((name . forty-two))
+(lambda-case ((() #f #f #f () ()) (const 42)
+  (toplevel forty-two))")
+(bytecode #f)
+(proc #f))
+(pass-if "compiling tree-il works"
+  (begin
+(set! bytecode
+  (call-with-input-string code
+(lambda (port)
+  (read-and-compile port #:from 'tree-il
+#t))
+(pass-if "bytecode can be read"
+  (begin
+(set! proc ((load-thunk-from-memory bytecode)))
+(procedure? proc)))
+(pass-if-equal "proc executes" 42 (proc
-- 
2.29.2






bug#44186: Recursive mkdir

2020-10-27 Thread Leo Prikler
Hello,

Am Dienstag, den 27.10.2020, 08:01 +0100 schrieb divoplade:
> I think I have made my point for the second commit. I intend this
> change to be user-centric: it would be better to confuse the
> developer
> of a guile program than the user of said program. Anyway, this will
> not
> confuse anyone because creating the directory is not the default
> behavior.
I am very not pleased with this distinction of "user" and "developer". 
Even assuming there is some, it would be wiser not to confuse the
latter, as then they can assure on their own terms, that the former
won't be confused either.  When you've reached the point, where "even"
the latter can't tell you what exactly will happen, how exactly are you
going to ensure the former won't be confused in the event something
*does* go wrong?  Spoiler warning: Returning #t on error won't actually
fix them.

> As for the first commit:
> 
> Le lundi 26 octobre 2020 à 22:05 +0100, Leo Prikler a écrit :
> > I'd prefer it if you didn't credit me for messing up your code. ;)
> > The reason my version works, is because (ice-9 filesystem) has a
> > working implementation of file-ancestors, which it uses to create
> > all
> > ancestors in order.  The part you've copied only creates one such
> > directory.  With the changes you've made, the directory, that does
> > get
> > created, is the first one in the tree, which does not exist.  I'm
> > surprised, that this test passes for you, because it does not pass
> > for
> > me.
> 
> Exactly, it does not pass the test, because I still can't run them.
That's one way to see it.  For the record, I didn't actually run your
test, but instead copied your code as well as a test into a separate
file rather than building all of guile.  That's an easier way of
prototyping.

> In which file do I write the code, I feel the ports module is not the
> best place.
That's because it isn't.  The only reason you could be led to putting
it there is because of your insistence on the second patch (recall,
that it is not at all needed) along with possibly a belief, that the
only reason to recursively make a directory is to put files at their
root, which is also wrong.

As I have been suggesting multiple times already, you could potentially
maybe drop your second patch without making much impact on those
developers and users, who receive an error when opening a file port
without ensuring the parent directory to exist.  That would allow you
to put mkdir-recursive into the posix module (and test it along with
it) even if it isn't strictly POSIX.  If that's not your cup of tea and
you have more than just mkdir-recursive to add, you might want to put
it into a different module.

Please also note, that Guile also doesn't particularly need *your*
implementation of mkdir-p (or mine for that matter).  Ludovic Courtès,
who you might remember being a co-maintainer along with Andy Wingo,
wrote mkdir-p for GNU Guix, so whether or not it gets included here is
much rather a question of whether or not they want to relicense it
under the LGPL.

> How do you run the tests? When I run "make check", I get 1 of 39
> tests failed, the test-out-of-memory test. It does not even try to
> run the ports test.
Have a look at test-suite/Makefile.am.

Regards, Leo






bug#44186: Recursive mkdir

2020-10-26 Thread Leo Prikler
Hello, divoplade,

> So, after a bit of bikeshedding on #guile, it turns out that the
> controversy moved to the second commit.
Not quite, the second commit just needlessly complicates a patch, that
should not be that complicated in the first place.
When posting a patch to the mailing list, you should do your best to
ensure, that all of your code works as intended (alas, I am getting
ahead of myself here a bit).  It is easier to prove this for *just*
mkdir-p/mkdir-recursive rather than having to test all your wrappers as
well.  You have been pretty adamant, that you need those wrappers, but
they can easily be written in whichever library actually ends up using
them as well, so you're just creating more work for yourself for little
gain.  That's the point I was trying to make in the IRC.

> Here is the justification for it.
> 
> When a program user wants to save data to a file, if the place where
> to
> save the file does not exist yet, there will be an error: "cannot
> create file or directory". That's puzzling to the user, because, yes,
> the user wants to create that file.  If the error is a little more
> precise, it will be something in the line of "Please create directory
> blah/blah/blah before invoking this program".
> 
> So, the user will wonder why the program was not able to create
> blah/blah/blah itself, create it, and re-run the program. This is
> more
> work for the user, work that could have been easily handled by the
> program.
That would be a nice story in a vacuum, but in practice few systems
work like that.  Python errors in a similar way to Guile, but with a
nicer message.  So does plain Node JS.  On the part of Node with all of
its single package modules, even there the equivalent to your versions
of open-output-file is one package[1] removed from the built-in fs
module.

> Good behaving programs should (recursively) create the directory
> before
> trying to write to a file specified by the user. That include log
> files
> for a daemon, for instance. Emacs org-mode babel tangling uses a
> :mkdirp t for a similar reason. In order to simplify the development
> of
> such programs, and in order to avoid bugs where the developer forgot
> to
> call (mkdir-recursive (dirname output-file)) before (open-output-
> file,
> call-with-output-file or with-output-to-file, while still keeping
> compatibility of the other programs, I propose to add a keyword
> argument to these functions. 
The programming of such procedures would not get simpler by inlining
mkdir-p into open-file.  Instead, programs written that way would be
harder to understand, as the implicit creation of such directories
outside of functions that explicitly exist for this implicit creation
will cause (some) confusion as to whether or not implicit creation of
directories can/will take place and whether that is actually wanted in
this context and erroring is not an acceptable alternative.

The addition of mkdir-p/mkdir-recursive/make-directories alone would
already enable a writer of org-mode tangle to write whichever helper
they need for their (in my opinion rather specific) use case in 2
lines. 

> I also simplified the mkdir-recursive function, to be closer to 
> https://gitlab.com/leoprikler/guile-filesystem/-/blob/master/ice-9/filesystem.scm
> .
I'd prefer it if you didn't credit me for messing up your code. ;)
The reason my version works, is because (ice-9 filesystem) has a
working implementation of file-ancestors, which it uses to create all
ancestors in order.  The part you've copied only creates one such
directory.  With the changes you've made, the directory, that does get
created, is the first one in the tree, which does not exist.  I'm
surprised, that this test passes for you, because it does not pass for
me.

On a somewhat related note, I am getting closer to a 0.1.0 release of
(ice-9 filesystem), which I'll then pitch to the Guix mailing lists. 
Being an outside module, that means you can use it with Guile versions
earlier than 3.0.5, which at least in my eyes sounds like a better
solution to your problem of not being able to implement org-babel in
Guile.

Regards, Leo

[1] https://www.npmjs.com/package/fse






bug#44050: [PATCH] doc-snarf: Add support for (ice-9 optargs) define*s.

2020-10-17 Thread Leo Prikler
* module/scripts/doc-snarf.scm (supported-languages)[scheme]: Limit
signature start to define and define*.
(peek-sexp): New variable.
(find-std-int-doc): Implement in terms of peek-sexp.
(parse-entry, make-prototype, get-symbol): Use full function definition
instead of def-line.
(snarf): Adjust accordingly.
(join-symbols): Removed variable.
(parse-defun): New variable.
---
 module/scripts/doc-snarf.scm | 141 +--
 1 file changed, 86 insertions(+), 55 deletions(-)

diff --git a/module/scripts/doc-snarf.scm b/module/scripts/doc-snarf.scm
index fa3dfb312..3dd714d9e 100644
--- a/module/scripts/doc-snarf.scm
+++ b/module/scripts/doc-snarf.scm
@@ -152,7 +152,7 @@ This procedure foos, or bars, depending on the argument 
@var{braz}.
  "^;;\\."
  "^;; (.*)"
  "^;;-(.*)"
- "^\\(define"
+ "^\\(define(\\*)?( |$)"
  #t
  )))
 
@@ -178,6 +178,14 @@ This procedure foos, or bars, depending on the argument 
@var{braz}.
   (write-output (snarf input lang) output
 (if texinfo? format-texinfo format-plain)))
 
+;; Read an s-expression from @var{port}, then rewind it, so that it can be
+;; read again.
+(define (peek-sexp port)
+  (let* ((pos (ftell port))
+ (sexp (read port)))
+(seek port pos SEEK_SET)
+sexp))
+
 ;; fixme: this comment is required to trigger standard internal
 ;; docstring snarfing...  ideally, it wouldn't be necessary.
 ;;-ttn-mod: new proc, from snarf-docs (aren't these names fun?)
@@ -185,7 +193,8 @@ This procedure foos, or bars, depending on the argument 
@var{braz}.
   "Unread @var{line} from @var{input-port}, then read in the entire form and
 return the standard internal docstring if found.  Return #f if not."
   (unread-string line input-port)   ; ugh
-  (let ((form (read input-port)))
+  (seek input-port -1 SEEK_CUR) ; ugh^2
+  (let ((form (peek-sexp input-port)))
 (cond ((and (list? form); (define (PROC ARGS) "DOC" ...)
 (< 3 (length form))
 (eq? 'define (car form))
@@ -270,9 +279,12 @@ return the standard internal docstring if found.  Return 
#f if not."
doc-strings (cons (match:substring m1 1) options) entries
(+ lno 1)))
   (m2
-(let ((options (augmented-options line i-p options))) ; ttn-mod
-  (lp (read-line i-p) 'neutral '() '()
-  (cons (parse-entry doc-strings options line input-file lno)
+(let* ((options (augmented-options line i-p options)) ; ttn-mod
+   (def (peek-sexp i-p)))
+  ;; due to the rewind in augmented-options and peek-sexp,
+  ;; we will actually see this line again, so read twice
+  (lp (begin (read-line i-p) (read-line i-p)) 'neutral '() '()
+  (cons (parse-entry doc-strings options def input-file lno)
 entries)
   (+ lno 1
(m3
@@ -295,9 +307,10 @@ return the standard internal docstring if found.  Return 
#f if not."
doc-strings (cons (match:substring m1 1) options) entries
(+ lno 1)))
   (m2
-(let ((options (augmented-options line i-p options))) ; ttn-mod
-  (lp (read-line i-p) 'neutral '() '()
-  (cons (parse-entry doc-strings options line input-file lno)
+(let* ((options (augmented-options line i-p options)) ; ttn-mod
+   (def (peek-sexp i-p)))
+  (lp (begin (read-line i-p) (read-line i-p)) 'neutral '() '()
+  (cons (parse-entry doc-strings options def input-file lno)
 entries)
   (+ lno 1
   (m3
@@ -326,13 +339,13 @@ return the standard internal docstring if found.  Return 
#f if not."
 
 ;; Create a docstring entry from the docstring line list
 ;; @var{doc-strings}, the option line list @var{options} and the
-;; define line @var{def-line}
-(define (parse-entry docstrings options def-line filename line-no)
+;; definition @var{def}
+(define (parse-entry docstrings options def filename line-no)
 ;  (write-line docstrings)
   (cond
-   (def-line
- (make-entry (get-symbol def-line)
-(make-prototype def-line) (reverse docstrings)
+   (def
+ (make-entry (get-symbol def)
+(make-prototype def) (reverse docstrings)
 (reverse options) filename
 (+ (- line-no (length docstrings) (length options)) 1)))
((> (length docstrings) 0)
@@ -347,48 +360,66 @@ return the standard internal docstring if found.  Return 
#f if not."
 
 ;; Create a string which is a procedure prototype.  The necessary
 ;; information for constructing the prototype is taken from the line
-;; @var{def-line}, which is a line starting with @code{(define...}.
-(define (make-prototype def-line)
-  (call-with-input-string
-   def-line
-   (lambda (s-p)
- (let* ((paren (read-char s-p))
-   (keyw

bug#43025: re-export-public-interface fails on Guile 3

2020-08-26 Thread Leo Prikler
Am Mittwoch, den 26.08.2020, 12:51 -0400 schrieb Dale Smith:
> On 8/24/20, Leo Prikler  wrote:
> > My solution for this problem would be to build on some of the
> > module
> > "intrinsics", which sadly are not all that well documented.
> > 
> > (define-module (a))
> > 
> > (let ((obs (module-obarray (resolve-interface '(srfi srfi-1
> >   (iface (module-public-interface (current-module
> >   (hash-fold
> >(lambda (key value seed)
> >  (module-add! iface key value)
> >  seed)
> >*unspecified*
> >obs))
> > 
> > If you want to make this a macro, you really only need to syntax-
> > unquote a module into the (resolve-interface ...) portion of this
> > snippet.
> 
> So with that in mind, how about something like this (currently no
> error checking):
> 
> (define-syntax re-export-public-interface
>   (syntax-rules ()
> ((_ mod ...)
>  (let ((iface (module-public-interface (current-module
>(module-for-each
> (lambda (sym val)
>   (module-add! iface sym val)
>   (hashq-set! (module-replacements iface) sym #t))
> (resolve-interface 'mod))
>...
LGTM, but don't forget to test it ;)
Also I'd call it `re-export-public-interface!', or even `re-export-and-
replace-interface!' but that's a personal preference.  






bug#43025: re-export-public-interface fails on Guile 3

2020-08-24 Thread Leo Prikler
Hi Dale.

Am Montag, den 24.08.2020, 12:11 -0400 schrieb Dale Smith:
> This is actually reported by daviid on IRC.
> 
> This definition of re-export-public-interface works fine on Guile
> 2.x,
> fails with Guile 3:
> (hope this makes it through the mails cleanly)

> ;;;
> ;;; re-export-public-interface
> ;;;
> 
> [...]
I'm going to skip the specifics of the macro here, it is not needed for
an MWE.

> ;;;
> ;;; A module that uses the above
> ;;;
> 
> 
> (define-module (a)
>   #:use-module (srfi srfi-1)
>   #:use-module (modules)
> 
>   #:export (map-a))
> 
> 
> (eval-when (expand load eval)
>   (re-export-public-interface (srfi srfi-1)))
> 
> 
> (define (map-a)
>   (map (lambda (item)
>  (display item)
>  (display " "))
> (iota 5))
>   (newline)
>   (values))
> 

For the sake of minimalism, I will shorten this to

(define-module (a))

(module-use! (module-public-interface (current-module))
 (resolve-interface '(srfi srfi-1)))

I hope you don't mind.

> [sessions]

My solution for this problem would be to build on some of the module
"intrinsics", which sadly are not all that well documented.

(define-module (a))

(let ((obs (module-obarray (resolve-interface '(srfi srfi-1
  (iface (module-public-interface (current-module
  (hash-fold
   (lambda (key value seed)
 (module-add! iface key value)
 seed)
   *unspecified*
   obs))

If you want to make this a macro, you really only need to syntax-
unquote a module into the (resolve-interface ...) portion of this
snippet.

Some sessions for reference:

GNU Guile 3.0.4
Copyright (C) 1995-2020 Free Software Foundation, Inc.

Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.

Enter `,help' for help.
scheme@(guile-user)> (add-to-load-path "/tmp")
scheme@(guile-user)> ,use (a)
;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;;   or pass the --no-auto-compile argument to disable.
;;; compiling /tmp/a.scm
;;; compiled $HOME/.cache/guile/ccache/3.0-LE-8-4.3/tmp/a.scm.go
scheme@(guile-user)> map
WARNING: (guile-user): imported module (a) overrides core binding `map'
$1 = #
scheme@(guile-user)> ,q

GNU Guile 3.0.2
Copyright (C) 1995-2020 Free Software Foundation, Inc.

Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.

Enter `,help' for help.
scheme@(guile-user)> (add-to-load-path "/tmp")
scheme@(guile-user)> ,use (a)
;;; note: source file /tmp/a.scm
;;;   newer than compiled /home/yuri/.cache/guile/ccache/3.0-LE-8-
4.2/tmp/a.scm.go
;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;;   or pass the --no-auto-compile argument to disable.
;;; compiling /tmp/a.scm
;;; compiled $HOME/.cache/guile/ccache/3.0-LE-8-4.2/tmp/a.scm.go
scheme@(guile-user)> map
WARNING: (guile-user): imported module (a) overrides core binding `map'
$1 = #
scheme@(guile-user)> ,q

GNU Guile 2.2.7
Copyright (C) 1995-2019 Free Software Foundation, Inc.

Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.

Enter `,help' for help.
scheme@(guile-user)> (add-to-load-path "/tmp")
scheme@(guile-user)> ,use (a)
;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;;   or pass the --no-auto-compile argument to disable.
;;; compiling /tmp/a.scm
;;; compiled $HOME/.cache/guile/ccache/2.2-LE-8-3.A/tmp/a.scm.go
scheme@(guile-user)> map
$1 = #
scheme@(guile-user)> ,q

Interestingly, versions newer than 3.0.2 warn about core override in
this case, as does #:re-export.  If I am not mistaken, this is the way
re-export works for variables normally.  module-use! on the other hand
does not modify the module-obarray, it instead adds the module to a
list of uses, which are handled separately in variable lookup during
`module_imported_variable`.  This C function does look up variables
exported by a module, but does not traverse module-uses recursively.  I
have no idea, how this works for 2.2.7, I only checked the Guile 3 code
here.

Regards,
Leo