define-module, #:export and export

2023-01-04 Thread yarl baudig
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

2023-01-04 Thread Maxime Devos

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

2023-01-04 Thread Jean Abou Samra

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