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
next prev parent 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).