Philip McGrath schreef op zo 20-02-2022 om 11:47 [-0500]: > I was just (or maybe am still?) dealing with some issues caused by cyclic > imports of package modules while updating Racket to 8.4: see in particular > , as well as #66, #112, and #113 in the > same thread. > [...] > I find the semantics of Guile's cyclic module imports very confusing, > and I don't know of any documentation for what is and isn't supported > other than advice from Ludo’ in —is there any? (The following explanation ignores syntax transformers, #:select and #:autoload.) Basically, a module consists of a hash table and a thunk initialising the hash table. A few situations to demonstrate: ;; Non-cyclic (define-module (foo) #:export (foo)) (define foo 0) (define-module (bar) #:export (bar) #:use-module (foo)) (define bar (+ 1 foo)) The thunk of 'foo' would be (lambda () (set-module-value! '(foo) 'foo 0)) and the thunk of 'bar' would be (lambda () ;; This calls the thunk of 'foo' if it hasn't yet been called ;; before. (initialise-module '(foo)) (set-module-value! '(bar) 'bar (+ 1 (module-value '(foo) 'foo 0)))) ;; Cyclic, non-problematic (define-module (foo) #:export (foo) #:use-module (bar)) (define foo 0) (define (calculate-foobar) (+ 1 (calculate-bar)) (define-module (bar) #:export (calculate-bar) #:use-module (foo)) (define (calculate-bar) (+ 1 foo)) The initialisation thunk of 'foo' would be (lambda () (initialise-module '(bar)) ; L1 (set-module-value! '(foo) 0) ; L2 (set-module-value! '(foo) 'calculate-foobar ; L3 (lambda () (+ 1 ((module-value '(bar) 'calculate-bar)))))) ; L4 and the thunk of 'bar' is: (lambda () (initialise-module '(foo)) ; L6 (set-module-value! '(bar) 'calculate-bar ; L7 (lambda () (+ 1 (module-value '(foo) 'foo))))) ; L8 Now let's see what happens if the module (bar) is loaded: ; Initialising '(bar)' (initialise-module '(foo)) ; L6 ;; (foo) has not yet begun initialisation, so run the thunk: -> (initialise-module '(bar)) ; L1 ;; (bar) is being initialised, so don't do anything here -> (set-module-value! '(foo) 0) ; L2 -> (set-module-value! '(foo) 'calculate-foobar ; L3 (lambda () (+1 ((module-value '(bar) 'calculate-bar)))) ; L4 The hash table of '(bar)' does not yet contain 'calculate-bar', but that's not a problem because the procedure 'calculate-foobar' is not yet run. (set-module-value! '(bar) '(calculate-bar) ; L7 (lambda () (+ 1 (module-value '(foo) 'foo)))) ; L8 ;; The hash table of '(foo)' contains 'foo', so no problem! ;; Alternatively, even if '(foo)' did not contain 'foo', ;; the procedure '(calculate-bar)' is not yet run, so no problem! ;; Done! Now for a problematic import cycle: (define-module (foo) #:export (foo) #:use-module (bar)) (define foo (+ 1 bar)) (define-module (bar) #:export (bar) #:use-module (foo)) (define bar (+ 1 foo)) Now let's reason what happens when we try importing 'bar'. The init thunk of '(foo)' is: (lambda () (initialise-module '(bar)) ; L1 (set-module-value! '(foo) 'foo (+ 1 (module-value '(bar) 'bar)))) ; L2 and the thunk of '(bar)': (lambda () (initialise-module '(foo)) ; L3 (set-module-value! '(bar) 'bar (+ 1 (module-value '(foo) 'foo)))) ; L4 Now let's see what happens if 'bar' is loaded: ; Initialising (bar) (initialise-module! '(foo)) ; L3 ;; (foo) has not yet begun initialisation, so run the thunk: -> (initialise-module '(bar)) ; L1 ;; (bar) is already initialising, so don't do anything -> (set-module-value! '(foo) 'foo (+ 1 bar))) Oops, the variable foo of the module (foo) has not yet been defined, so an 'unbound-variable' exception is raised! I hope that illustrates a little when and when not cyclic imports work! Greetings, Maxime.