guile-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: for with break and continue


From: Jean Abou Samra
Subject: Re: for with break and continue
Date: Sun, 4 Sep 2022 12:43:58 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Thunderbird/102.2.0

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 via email to

[Prev in Thread] Current Thread [Next in Thread]