unofficial mirror of guile-user@gnu.org 
 help / color / mirror / Atom feed
* Does declaration order matter in guile?
@ 2023-02-12 18:46 wolf
  2023-02-12 19:52 ` Taylan Kammer
  2023-02-13 17:10 ` Maxime Devos
  0 siblings, 2 replies; 8+ messages in thread
From: wolf @ 2023-02-12 18:46 UTC (permalink / raw)
  To: guile-user

[-- Attachment #1: Type: text/plain, Size: 2578 bytes --]

Hello,

I had encountered interesting thing yesterday, which challenged my
understanding of guile (scheme). I always assumed that order of definitions in
scheme does not matter, as long as everything if defined when it is running.

So this should (and does) work:

    (define (x) (y))
    (define (y) (display "foo\n"))
    (x)

However, then I wanted to used SRFI-9. I did use similar pattern, and it
failed. The test code for that is:

    (use-modules (srfi srfi-9))
    
    (define (x y)
      (display (foo y))
      (newline))
    
    (define-record-type q
      (make-q foo)
      q?
      (foo foo))
    
    (x (make-q "1"))

The error I got was:

    Backtrace:
    In ice-9/boot-9.scm:
      1752:10  7 (with-exception-handler _ _ #:unwind? _ # _)
    In unknown file:
               6 (apply-smob/0 #<thunk 7fa9a5866340>)
    In ice-9/boot-9.scm:
        724:2  5 (call-with-prompt ("prompt") #<procedure 7fa9a5872080 …> …)
    In ice-9/eval.scm:
        619:8  4 (_ #(#(#<directory (guile-user) 7fa9a5869c80>)))
    In ice-9/boot-9.scm:
       2836:4  3 (save-module-excursion #<procedure 7fa9a585b300 at ice-…>)
      4388:12  2 (_)
    In /tmp/q/srfi-9.scm:
         4:11  1 (x _)
         4:11  0 (x _)
    
    /tmp/q/srfi-9.scm:4:11: In procedure x:
    Wrong type to apply: #<syntax-transformer foo>

When I move the (define-record-type ...) call above (define (x...) it starts to
works.

Also, I could not help to notice that when I use R6RS records it does work
regardless of the order:

    (use-modules (rnrs records syntactic))
    
    (define (x y)
      (display (q-foo y))
      (newline))
    
    (define-record-type q (fields foo))
    
    (x (make-q "1"))

So, I have few questions I would like to ask:

1. When does order matter? What is going on here?
2. Why does guile recommend SRFI-9 over the R6RS records? They seem less
   verbose and more robust. At least to novice like me.
3. What does guile implement by default? There are --r6rs and --r7rs arguments,
   what scheme is used when neither is supplied? R5RS? Sorry if this is stupid
   question, the scheme landscape seems... complicated.
4. Is the (install-r6rs!) global and affecting all reading from that point on
   or is it scoped to the file being currently read? I ask because I am curious
   if I can mix files using R6RS and R7RS in one program.

Thank you very much,
W.

-- 
There are only two hard things in Computer Science:
cache invalidation, naming things and off-by-one errors.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Does declaration order matter in guile?
  2023-02-12 18:46 Does declaration order matter in guile? wolf
@ 2023-02-12 19:52 ` Taylan Kammer
  2023-02-13  8:05   ` Sascha Ziemann
  2023-02-14 20:00   ` wolf
  2023-02-13 17:10 ` Maxime Devos
  1 sibling, 2 replies; 8+ messages in thread
From: Taylan Kammer @ 2023-02-12 19:52 UTC (permalink / raw)
  To: guile-user

On 12.02.2023 19:46, wolf wrote:

> So, I have few questions I would like to ask:
> 
> 1. When does order matter? What is going on here?

Heya.

The order matters in this case because the SRFI-9 implementation in Guile defines
syntax (macros) rather than just variables bound to procedures.

If you use an undefined variable in a lambda body like '(define (blah) <HERE>)'
then it will be compiled into a variable lookup in the global environment, so you
can make it work by later defining that variable to something appropriate.

However, the "macro expansion" needs to happen immediately, so what happens here is:

1. (foo y) is compiled into code that will:
   - Look up the global variable 'foo'
   - Try to "apply" its value to the value of 'y'

2. You then bind 'foo' to a "syntax transformer" (macro) in the global environment.

3. When the code is executed, it tries to apply the syntax transformer as if it were
   a procedure or another type that can be "applied," which fails, because syntax
   transformers are not a type that can be "applied" like procedures and such.

(The error message would be clearer if it said "is not a procedure" instead of "wrong
type to apply" but there's other types in Guile that can be "applied" and not just
procedures, hence that slightly less clear error message.)

What *needs* to happen for it to work instead, is:

1. You bind 'foo' to the syntax transformer.

2. During the compilation of (foo y), the compiler calls the syntax transformer to
   affect the generation of code, so it will do the right thing.

> 2. Why does guile recommend SRFI-9 over the R6RS records? They seem less
>    verbose and more robust. At least to novice like me.

SRFI-9 is smaller and more widespread.  Ultimately, it's a matter of taste, since
both have advantages and disadvantages, and some things that are either an advantage
or a disadvantage depending on who you ask. :-)

> 3. What does guile implement by default? There are --r6rs and --r7rs arguments,
>    what scheme is used when neither is supplied? R5RS? Sorry if this is stupid
>    question, the scheme landscape seems... complicated.

No worries, not a stupid question at all.  What Guile uses by default could be called
"Guile Scheme."  It neither completely fulfills the requirements of R7RS or R6RS, nor
is it limited to either.  I think that's what most Scheme implementations do...  The
standardization of Scheme is in a sad state.

> 4. Is the (install-r6rs!) global and affecting all reading from that point on
>    or is it scoped to the file being currently read? I ask because I am curious
>    if I can mix files using R6RS and R7RS in one program.

I'm not 100% sure on this one, but I don't think it can affect the reading of the file
it appears in, because the entirety of reading happens before anything is executed.

It should affect *explicit* reading you do from that point on, i.e., what the 'read'
procedure will do with its input.

> Thank you very much,
> W.
> 

You're welcome, and have fun with Guile!

-- 
Taylan




^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Does declaration order matter in guile?
  2023-02-12 19:52 ` Taylan Kammer
@ 2023-02-13  8:05   ` Sascha Ziemann
  2023-02-13 10:44     ` Dr. Arne Babenhauserheide
  2023-02-13 17:07     ` Maxime Devos
  2023-02-14 20:00   ` wolf
  1 sibling, 2 replies; 8+ messages in thread
From: Sascha Ziemann @ 2023-02-13  8:05 UTC (permalink / raw)
  To: guile-user

Am So., 12. Feb. 2023 um 20:52 Uhr schrieb Taylan Kammer
<taylan.kammer@gmail.com>:
>
> On 12.02.2023 19:46, wolf wrote:
>
> > 1. When does order matter? What is going on here?
>
> The order matters in this case because the SRFI-9 implementation in Guile defines
> syntax (macros) rather than just variables bound to procedures.

This is a huge problem of Scheme in general, that you can not
distinguish between
procedures and macros just by looking at the code. You have to know it
or you have
to look it up in the manual.

You also can not ask Scheme about macros, because macros are not
first-class-citizens.
This might be interesting:
https://matt.might.net/articles/metacircular-evaluation-and-first-class-run-time-macros/



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Does declaration order matter in guile?
  2023-02-13  8:05   ` Sascha Ziemann
@ 2023-02-13 10:44     ` Dr. Arne Babenhauserheide
  2023-02-13 17:07     ` Maxime Devos
  1 sibling, 0 replies; 8+ messages in thread
From: Dr. Arne Babenhauserheide @ 2023-02-13 10:44 UTC (permalink / raw)
  To: Sascha Ziemann; +Cc: guile-user

[-- Attachment #1: Type: text/plain, Size: 1332 bytes --]


Sascha Ziemann <ceving@gmail.com> writes:

> Am So., 12. Feb. 2023 um 20:52 Uhr schrieb Taylan Kammer
> <taylan.kammer@gmail.com>:
>>
>> On 12.02.2023 19:46, wolf wrote:
>>
>> > 1. When does order matter? What is going on here?
>>
>> The order matters in this case because the SRFI-9 implementation in Guile defines
>> syntax (macros) rather than just variables bound to procedures.
>
> This is a huge problem of Scheme in general, that you can not
> distinguish between
> procedures and macros just by looking at the code. You have to know it
> or you have
> to look it up in the manual.

I see this as a strength, because it allows me to start with a procedure
and switch it to be a macro later on if I need to.

That way I can start with the weaker but easier to reason about
construct and move to the stronger one when needed.

> You also can not ask Scheme about macros, because macros are not
> first-class-citizens.

Actually you can, though you need to take a detour through modules:

(define-syntax-rule (foo) #f)
(define (bar) #f)

(macro? (module-ref (current-module) 'foo))
;; => #t
(macro? (module-ref (current-module) 'bar))
;; => #f

(that’s from the info reference manual)

Best wishes,
Arne
-- 
Unpolitisch sein
heißt politisch sein,
ohne es zu merken.
draketo.de

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 1125 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Does declaration order matter in guile?
  2023-02-13  8:05   ` Sascha Ziemann
  2023-02-13 10:44     ` Dr. Arne Babenhauserheide
@ 2023-02-13 17:07     ` Maxime Devos
  1 sibling, 0 replies; 8+ messages in thread
From: Maxime Devos @ 2023-02-13 17:07 UTC (permalink / raw)
  To: Sascha Ziemann, guile-user


[-- Attachment #1.1.1: Type: text/plain, Size: 2777 bytes --]



On 13-02-2023 09:05, Sascha Ziemann wrote:
> You also can not ask Scheme about macros, because macros are not
> first-class-citizens.
 >
 >
 > This might be interesting:
 > 
https://matt.might.net/articles/metacircular-evaluation-and-first-class-run-time-macros/

You actually can ask (Guile)Scheme about macros, and they are 
first-class (just not procedures, and almost never actually used as 
first-class things):

;; first-class: macros are just a type of values
(let ((the-case-macro (module-ref (current-module) 'case)))
   (pk the-case-macro) ; -> #<syntax-transformer case>
   ;; This type is disjoint from procedures:
   (pk (procedure? the-case-macro)) ; -> #false
   (pk (macro? the-case-macro)) ; -> #true
   ;; You can use macros to transform syntax to new syntax at runtime:
   (pk (procedure? (macro-transformer the-case-macro))) ; -> #true
   (pk ((macro-transformer the-case-macro)
         #'(case number
            ((one) 1)
            ((two) 2)))) ; -> #<syntax: [lots of stuff]>
   ;; You can make macros at runtime (though usually you would just
   ;; pass syntax-transforming procedures instead of the macro wrapper
   ;; type):
   (pk (make-syntax-transformer 'pick-a-name 'macro (lambda (s) #'0)))
   (values))

While unconventional, in principle there is nothing stopping you 
(besides cross-Scheme compatibility) from using a combination of 
'let-syntax-syntax', 'syntax-case' and 'syntax-local-binding' to make 
let macros accept macros as arguments.  Example:

(use-modules (system syntax))
(define-syntax call-macro-for-each
   (lambda (s)
     (syntax-case s ()
       ((_ macro-identifier arg ...)
        (call-with-values
          (lambda ()
            ;; Note: syntax-local-binding implicitly calls macro-transformer
            (syntax-local-binding #'macro-identifier))
          (lambda (type value)
            (unless (eq? type 'macro)
              (error "first argument to call-macro-for-each must be an 
identifier of a macro"))
            (let loop ((arguments #'(arg ...)))
              (syntax-case arguments ()
                ((last) (value #'last))
                ((stuff . more-stuff)
                 #`(begin #,(value #'stuff)
                          #,(loop #'more-stuff)))))))))))

(let-syntax ((pk+quote
                (lambda (s)
                  #`(pk '#,s '-> #,s))))
   (call-macro-for-each pk+quote (+ 1 1) (+ 1 2) (+ 1 3)))
;; Output:
;;; ((+ 1 1) -> 2)

;;; ((+ 1 2) -> 3)

;;; ((+ 1 3) -> 4)
$1 = 4

The only snag here is that each macro you want to pass to a 
'higher-order macro', you need to give an identifier.  As you can always 
do that with 'let-syntax', that doesn't make it non-first class IMO.

Greetings,
Maxime.

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 929 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 236 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Does declaration order matter in guile?
  2023-02-12 18:46 Does declaration order matter in guile? wolf
  2023-02-12 19:52 ` Taylan Kammer
@ 2023-02-13 17:10 ` Maxime Devos
  1 sibling, 0 replies; 8+ messages in thread
From: Maxime Devos @ 2023-02-13 17:10 UTC (permalink / raw)
  To: guile-user


[-- Attachment #1.1.1: Type: text/plain, Size: 813 bytes --]



On 12-02-2023 19:46, wolf wrote:
> Also, I could not help to notice that when I use R6RS records it does work
> regardless of the order:
> 
>      (use-modules (rnrs records syntactic))
>      
>      (define (x y)
>        (display (q-foo y))
>        (newline))
>      
>      (define-record-type q (fields foo))
>      
>      (x (make-q "1"))
> 
> So, I have few questions I would like to ask:
> 
> 1. When does order matter? What is going on here?

IIUC, the difference here between SRFI and R6RS, is that the SRFI tries 
to inline the constructor via macros -- in SRFI-9, 'foo' is a macro, 
whereas in R6RS, it is a procedure.

I also might be incorrect -- while I have once looked at the R6RS and 
SRFI records implementation, I might have misremembered.

Greetings,
Maxime.

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 929 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 236 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Does declaration order matter in guile?
  2023-02-12 19:52 ` Taylan Kammer
  2023-02-13  8:05   ` Sascha Ziemann
@ 2023-02-14 20:00   ` wolf
  2023-02-14 20:26     ` Dr. Arne Babenhauserheide
  1 sibling, 1 reply; 8+ messages in thread
From: wolf @ 2023-02-14 20:00 UTC (permalink / raw)
  To: Taylan Kammer; +Cc: guile-user

[-- Attachment #1: Type: text/plain, Size: 4034 bytes --]

Hello,

first, let me thank you for thorough explanation, it helped and I (hopefully)
now have better understanding. Few more questions are below.

On 2023-02-12 20:52:16 +0100, Taylan Kammer wrote:
> On 12.02.2023 19:46, wolf wrote:
> 
> > So, I have few questions I would like to ask:
> > 
> > 1. When does order matter? What is going on here?
> 
> Heya.
> 
> The order matters in this case because the SRFI-9 implementation in Guile defines
> syntax (macros) rather than just variables bound to procedures.
> 
> If you use an undefined variable in a lambda body like '(define (blah) <HERE>)'
> then it will be compiled into a variable lookup in the global environment, so you
> can make it work by later defining that variable to something appropriate.
> 
> However, the "macro expansion" needs to happen immediately, so what happens here is:
> 
> 1. (foo y) is compiled into code that will:
>    - Look up the global variable 'foo'
>    - Try to "apply" its value to the value of 'y'
> 
> 2. You then bind 'foo' to a "syntax transformer" (macro) in the global environment.
> 
> 3. When the code is executed, it tries to apply the syntax transformer as if it were
>    a procedure or another type that can be "applied," which fails, because syntax
>    transformers are not a type that can be "applied" like procedures and such.
> 
> (The error message would be clearer if it said "is not a procedure" instead of "wrong
> type to apply" but there's other types in Guile that can be "applied" and not just
> procedures, hence that slightly less clear error message.)
> 
> What *needs* to happen for it to work instead, is:
> 
> 1. You bind 'foo' to the syntax transformer.
> 
> 2. During the compilation of (foo y), the compiler calls the syntax transformer to
>    affect the generation of code, so it will do the right thing.
> 

Interesting, I think I understand the difference. So in some ways this can be
compared to the C pre-processor? Is there a way to view the resulting code
after all the transformations were applied?

> > 2. Why does guile recommend SRFI-9 over the R6RS records? They seem less
> >    verbose and more robust. At least to novice like me.
> 
> SRFI-9 is smaller and more widespread.  Ultimately, it's a matter of taste, since
> both have advantages and disadvantages, and some things that are either an advantage
> or a disadvantage depending on who you ask. :-)
> 
> > 3. What does guile implement by default? There are --r6rs and --r7rs arguments,
> >    what scheme is used when neither is supplied? R5RS? Sorry if this is stupid
> >    question, the scheme landscape seems... complicated.
> 
> No worries, not a stupid question at all.  What Guile uses by default could be called
> "Guile Scheme."  It neither completely fulfills the requirements of R7RS or R6RS, nor
> is it limited to either.  I think that's what most Scheme implementations do...  The
> standardization of Scheme is in a sad state.

Meaning that if I want to use the many guile- libraries available under guix
and elsewhere, the most pragmatic approach would be to just not care about R6RS
vs R7RS all that much, and just do what guile manual recommends. Correct?

> 
> > 4. Is the (install-r6rs!) global and affecting all reading from that point on
> >    or is it scoped to the file being currently read? I ask because I am curious
> >    if I can mix files using R6RS and R7RS in one program.
> 
> I'm not 100% sure on this one, but I don't think it can affect the reading of the file
> it appears in, because the entirety of reading happens before anything is executed.
> 
> It should affect *explicit* reading you do from that point on, i.e., what the 'read'
> procedure will do with its input.
> 
> > Thank you very much,
> > W.
> > 
> 
> You're welcome, and have fun with Guile!

Thanks again and have a nice day,
W.

-- 
There are only two hard things in Computer Science:
cache invalidation, naming things and off-by-one errors.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Does declaration order matter in guile?
  2023-02-14 20:00   ` wolf
@ 2023-02-14 20:26     ` Dr. Arne Babenhauserheide
  0 siblings, 0 replies; 8+ messages in thread
From: Dr. Arne Babenhauserheide @ 2023-02-14 20:26 UTC (permalink / raw)
  To: wolf; +Cc: Taylan Kammer, guile-user

[-- Attachment #1: Type: text/plain, Size: 2071 bytes --]


wolf <wolf@wolfsden.cz> writes:

>> 1. You bind 'foo' to the syntax transformer.
>> 
>> 2. During the compilation of (foo y), the compiler calls the syntax transformer to
>>    affect the generation of code, so it will do the right thing.
>
> Interesting, I think I understand the difference. So in some ways this can be
> compared to the C pre-processor? Is there a way to view the resulting code
> after all the transformations were applied?

In the guile shell you can use

,expand (foo 1)

to see what it expands to.

Also see

,h compile

> Meaning that if I want to use the many guile- libraries available under guix
> and elsewhere, the most pragmatic approach would be to just not care about R6RS
> vs R7RS all that much, and just do what guile manual recommends. Correct?

R7RS vs. R6RS isn’t that big of a difference in Guile, because it mostly
supports both and you can mostly use either of them.

R7RS is cleaner is some ways and managed to re-unite some of the Scheme
implementations (that were split between R5RS and R6RS). So knowing R7RS
means that you can make your code run in a much larger number of
domains, and you can keep many of your skills even when moving from
mostly server-side webdev (i.e. Chicken or Guile¹) to embedded (Chibi),
or to shipping binaries(i.e. Gambit or bigloo), or even Java/JWM (kawa),
or to others (see https://ecraven.github.io/r7rs-benchmarks/).

Why Guile? https://www.draketo.de/software/guile-10x
Why others? https://wingolog.org/archives/2013/01/07/an-opinionated-guide-to-scheme-implementations

¹: partially moving into clientside via Guilescript or others:
   https://www.draketo.de/software/wisp#guilescript-2023-01-10
   https://srfi-email.schemers.org/schemeweb/msg/11606995/
   https://github.com/schemeweb/wiki/wiki/frontend

Also Spritely started working on Guile on WebAssembly:
https://spritely.institute/news/guile-on-web-assembly-project-underway.html

Best wishes,
Arne
-- 
Unpolitisch sein
heißt politisch sein,
ohne es zu merken.
draketo.de

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 1125 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2023-02-14 20:26 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-02-12 18:46 Does declaration order matter in guile? wolf
2023-02-12 19:52 ` Taylan Kammer
2023-02-13  8:05   ` Sascha Ziemann
2023-02-13 10:44     ` Dr. Arne Babenhauserheide
2023-02-13 17:07     ` Maxime Devos
2023-02-14 20:00   ` wolf
2023-02-14 20:26     ` Dr. Arne Babenhauserheide
2023-02-13 17:10 ` Maxime Devos

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).