unofficial mirror of guile-user@gnu.org 
 help / color / mirror / Atom feed
From: Jean Abou Samra <jean@abou-samra.fr>
To: Damien Mattei <damien.mattei@gmail.com>, guile-user <guile-user@gnu.org>
Subject: Re: for with break and continue
Date: Sun, 4 Sep 2022 12:43:58 +0200	[thread overview]
Message-ID: <89d7a30c-9a36-00b9-49d1-a28b0d776ce3@abou-samra.fr> (raw)
In-Reply-To: <CADEOadf3qBUbRgC4W1YDKFRWhFhR52vg5f2ZUh5Vqv-TfeehcA@mail.gmail.com>

Le 04/09/2022 à 11:54, Damien Mattei a écrit :
> i try to make a for with break and continue the way C language do it, i
> works with break but if i add a continue feature i then loose the break
> feature, here is my code:
> (define-syntax for/bc
>
>    (lambda (stx)
>      (syntax-case stx ()
>        ((kwd (init test incrmt) body ...)
>
>         (with-syntax
> ((BREAK (datum->syntax #'kwd 'break)))
>
> #'(call/cc
>    (lambda (escape)
>      (let-syntax
> ((BREAK (identifier-syntax (escape))))
>        init
>        (let loop ()
> (when test
>
>        (with-syntax
> ((CONTINUE (datum->syntax #'kwd 'continue)))
>
> #'(call/cc
>   (lambda (next)
>     (let-syntax
> ((CONTINUE (identifier-syntax (next))))
>       body ...)))
>
>        incrmt
>        (loop))))))))))))



The problem is with the meta level vs. the expanded output level. You have
two nested levels of #' . This (with-syntax ((CONTINUE ...)) ...) is part of
the expanded output, it doesn't run when your macro is expanded. The body of
the (expanded) loop just returns a syntax object. That's not what you want.
Here's a definition that works:

(define-syntax for/bc
   (lambda (stx)
     (syntax-case stx ()
       ((kwd (init test incrmt) body ...)
        (with-syntax ((BREAK (datum->syntax #'kwd 'break))
                      (CONTINUE (datum->syntax #'kwd 'continue)))
          #'(call/cc
             (lambda (escape)
               (let-syntax ((BREAK (identifier-syntax (escape))))
                 init
                 (let loop ()
                   (when test
                     (call/cc
                      (lambda (next)
                        (let-syntax ((CONTINUE (identifier-syntax (next))))
                          body ...)))
                       incrmt
                       (loop)))))))))))

(let ((i #f))
   (for/bc ((set! i 0) (< i 10) (set! i (1+ i)))
     (when (< i 5)
       continue)
     (when (> i 9)
       break)
     (display i)
     (newline)))



You could also use quasisyntax (#` and #,) to get the same effect:


(define-syntax for/bc
   (lambda (stx)
     (syntax-case stx ()
       ((kwd (init test incrmt) body ...)
        #`(call/cc
           (lambda (escape)
             (let-syntax ((#,(datum->syntax #'kwd 'break)
                           (identifier-syntax (escape))))
               init
               (let loop ()
                 (when test
                   (call/cc
                    (lambda (next)
                      (let-syntax ((#,(datum->syntax #'kwd 'continue)
                                    (identifier-syntax (next))))
                        body ...)))
                   incrmt
                   (loop))))))))))



That said, I would recommend using syntax parameters for break and continue.
They're cleaner, since they can be rebound by the user. Also, you can
use let/ec from (ice-9 control) instead of call/cc. It's more efficient
because it doesn't need to actually reify the whole environment, since
an escape continuation is upwards-only (it can be used inside the expression
to escape it, but it can't be used outside to reinstate its context).


(use-modules (ice-9 control))

(define-syntax-parameter break
   (lambda (sintax)
     (syntax-violation 'break "break outside of for/bc" sintax)))

(define-syntax-parameter continue
   (lambda (sintax)
     (syntax-violation 'continue "continue outside of for/bc" sintax)))

(define-syntax-rule (for/bc (init test increment) body body* ...)
   (begin
     init
     (let/ec escape
       (syntax-parameterize ((break (identifier-syntax (escape))))
         (let loop ()
           (when test
             (let/ec next
               (syntax-parameterize ((continue (identifier-syntax (next))))
                 body body* ...))
             increment
             (loop)))))))

(let ((i #f))
   (for/bc ((set! i 0) (< i 10) (set! i (1+ i)))
     (when (< i 5)
       continue)
     (when (> i 9)
       break)
     (display i)
     (newline)))




And here's an example showing the benefits of syntax parameters.
Add at the beginning of the code above:


(define-module (for)
   #:export (break continue for/bc))


In the same directory, put a file rename.scm containing:

(use-modules ((for)
               #:select ((break . for-break) continue for/bc))
              (srfi srfi-1) ; contains a break procedure
              (ice-9 receive))

(let ((i #f))
   (for/bc ((set! i 0) (< i 10) (set! i (1+ i)))
     (receive (before after)
       (break (lambda (x)
                (> x 5))
              (iota i))
       (when (pair? after)
         for-break)
       (display i))))



And run as

guile -L . rename.scm

As you can see, syntax parameters enable the code to use 'break'
for something else.

A final note: are you aware of the existence of 'do' in Scheme?
Most cases of a C for loop can be written elegantly using do.

https://www.gnu.org/software/guile/manual/html_node/while-do.html

Regards,
Jean




  reply	other threads:[~2022-09-04 10:43 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-09-04  9:54 for with break and continue Damien Mattei
2022-09-04 10:43 ` Jean Abou Samra [this message]
2022-09-04 12:00   ` Damien Mattei
2022-09-04 12:40     ` Jean Abou Samra
2022-09-04 14:41       ` Damien Mattei
2022-09-05  0:42         ` Jean Abou Samra
2022-09-05  8:46           ` Damien Mattei

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/guile/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=89d7a30c-9a36-00b9-49d1-a28b0d776ce3@abou-samra.fr \
    --to=jean@abou-samra.fr \
    --cc=damien.mattei@gmail.com \
    --cc=guile-user@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).