define-module, #:export and export
Hello guile. I don't know if that's a bug. Anyway, I am confused about this so I ask. I came across this problem playing with guix source code. I will share different "tests" each test is a directory with nothing but the files I share. each time the command to try the test (inside it's directory) is `guile --no-auto-compile -L . main.scm` base/ main.scm: - (define-module (main) #:export (valid-path?)) (define-syntax define-enumerate-type (syntax-rules () ((_ name->int (name id) ...) (define-syntax name->int (syntax-rules (name ...) ((_ name) id) ...) (define-enumerate-type operation-id (valid-path? 1)) (define-syntax operation (syntax-rules () ((operation name) (lambda () (simple-format #t "~S\n" (operation-id name)) (+ 3 (operation-id name)) (define-syntax define-operation (syntax-rules () ((prout (name)) (define name (operation name) (define-operation (valid-path?)) (simple-format #t "~S\n" (operation-id valid-path?)) - RUNS split-base/ lib.scm: - (define-module (lib) #:export (operation-id)) (define-syntax define-enumerate-type (syntax-rules () ((_ name->int (name id) ...) (define-syntax name->int (syntax-rules (name ...) ((_ name) id) ...) (define-enumerate-type operation-id (valid-path? 1)) - main.scm: - (define-module (main) #:use-module (lib) ;; #:export (valid-path?)) (define-syntax operation (syntax-rules () ((operation name) (lambda () (simple-format #t "~S\n" (operation-id name)) (+ 3 (operation-id name)) (define-syntax define-operation (syntax-rules () ((prout (name)) (define name (operation name) (define-operation (valid-path?)) ;; (export valid-path?) ;; (simple-format #t "~S\n" (operation-id valid-path?)) - Now. this RUNS. There is 3 tests from this "split-base" split-define-module-export: from split-base, uncomment "#:export (valid-path?)" : FAILS Note the difference with "base", which RUNS. split-export : from split-base (so, comment again #:export), uncomment "(export valid-path?)" : RUNS Here I note that there is a difference between #:export (...) and (export ...). Is this a bug? Or an undocumented feature? Or a misunderstanding from me? split-simple-format : from split-base, uncomment "(simple-format ...)" : FAILS. Here the "interesting" difference is between base and split-base. I am really confused there and I am asking for comments, please :). Thank you!
Re: define-module, #:export and export
On 04-01-2023 16:11, yarl baudig wrote: Hello guile. I don't know if that's a bug. Anyway, I am confused about this so I ask. I came across this problem playing with guix source code. I will share different "tests" each test is a directory with nothing but the files I share. each time the command to try the test (inside it's directory) is `guile --no-auto-compile -L . main.scm` [...] My (untested) hypothesis (*: things I'm unsure about) If there is no #:export (...), then when (define-operation (valid-path?)) is expanded, the identifier 'valid-path?' is free (*). Hence, the following ... > (syntax-rules (name ...) > ((_ name) id) ...) refers to the free identifier 'valid-path?'. Conversely, if there is #:export, then the (syntax-rules (name ...) ...) looks for the bound identifier 'valid-path?' (*). Important: bound identifiers != free-identifier, even if they have the same symbol! For (simple-format #t "~S\n" (operation-id valid-path?)) to work, the 'valid-path?' of that expression must be bound if the syntax-rules looks for a bound identifier, and free if it looks for a free identifier. By adding a (export valid-path?) between the simple-format and the (define-operation ...), the 'valid-path?' in (simple-format ...) becomes a bound identifier (*). Proposed solutions: (a) Move the (export ...) after the (simple-format ...) instead of in-between the define-operation and simple-format (b) Or put the uses of define-operation and define-enumeration-type in the same file. (c) Or use (eq? (syntax->datum ...) ...) instead of the 'syntax-rules (...)', to ignore the lexical environment and hence the bound/free distinction. (This has consequences for users of 'main' that rename their import of 'valid-path?' -- by this change, operation-id will expect the unrenamed name, whereas in the original code (IIUC) it would expect the renamed name.) Greetings, Maxime. OpenPGP_0x49E3EE22191725EE.asc Description: OpenPGP public key OpenPGP_signature Description: OpenPGP digital signature
Re: define-module, #:export and export
Le 04/01/2023 à 18:28, Maxime Devos a écrit : On 04-01-2023 16:11, yarl baudig wrote: Hello guile. I don't know if that's a bug. Anyway, I am confused about this so I ask. I came across this problem playing with guix source code. I will share different "tests" each test is a directory with nothing but the files I share. each time the command to try the test (inside it's directory) is `guile --no-auto-compile -L . main.scm` [...] My (untested) hypothesis (*: things I'm unsure about) If there is no #:export (...), then when (define-operation (valid-path?)) is expanded, the identifier 'valid-path?' is free (*). Hence, the following ... > (syntax-rules (name ...) > ((_ name) id) ...) refers to the free identifier 'valid-path?'. Conversely, if there is #:export, then the (syntax-rules (name ...) ...) looks for the bound identifier 'valid-path?' (*). Important: bound identifiers != free-identifier, even if they have the same symbol! For (simple-format #t "~S\n" (operation-id valid-path?)) to work, the 'valid-path?' of that expression must be bound if the syntax-rules looks for a bound identifier, and free if it looks for a free identifier. That is also my understanding, confirmed by $ cat lib.scm (define-module (lib) #:export (test)) (define-syntax test (lambda (sintax) (syntax-case sintax () ((test id) (datum->syntax sintax (free-identifier=? #'id #'thing)) $ cat test.scm (define-module (main) #:use-module (lib) #:export (thing) ) (display (test thing)) (newline) (define thing 5) $ guile3.0 -L . test.scm #f If you comment out #:export (thing), the result changes to #t. To put it perhaps more simply, the use of #:export causes Guile to understand early that there will be a variable 'thing' in this module, and makes the identifier 'thing' refer to this variable that is not yet defined. However, hygiene implies that you want to be able to use keywords as if they were not keywords if they are rebound, e.g. the 'else' here doesn't cause the cond clause to be taken: (let ((else #f)) (cond (#f 'bla) (else 'foo) (#t 'bar))) $1 = bar The way this is done is by comparing with the original identifier given to syntax-rules. Technically, they are compared with free-identifier=? . This means that a use of the identifier matches the keyword iff both are unbound, or both are bound to the same lexical binding. However, this isn't the case here, as the keyword in the macro was unbound, but at the point of use, it has been bound by #:export. Honestly, I think it is better to choose a different way of writing these macros that avoids this confusing issue. Try defining the operations at the same time as the enum so that the macro giving an enum member refers to the bindings of the operators. If you give more context on what you're trying to do, we could help more. Best, Jean OpenPGP_signature Description: OpenPGP digital signature