2

Note: This appears to be Gauche Scheme version 0.9.3.3.
I cannot seem to wrap my head around these Lisp languages :/.

I'm trying to define a for loop syntax in Scheme. I'm not sure if this is doable with a recursive function (I think it is), but at this point I really want to get define-syntax working.

I can get the loop to run once (and then it looks like it terminates) with this code:

(define-syntax forloop
  (syntax-rules ()
    ((forloop start stop exps)
      (letrec ((aloop (lambda (astart astop aexps)
        (if (<= astart astop) (begin aexps (aloop (+ astart 1) astop aexps))))))
          (aloop start stop exps)))))

It would be nice to also be able to define it with an ellipsis, but this gives "a template contains repetition of constant form". I have read the macro section of the R5RS spec, though I will be rereading the template section soon:

(define-syntax forloop
  (syntax-rules ()
    ((forloop start stop exps ...)
      (letrec ((aloop (lambda (astart astop aexps ...)
        (if (<= astart astop) (begin aexps ... (aloop (+ astart 1) astop aexps ...))))))
        (aloop start stop exps ...)))))

I tried this first, but it runs for about 10 seconds before failing without any output... I'm using compileonline.com, so that may have something to do with it:

(define-syntax forloop
  (syntax-rules ()
    ((forloop start stop exps ...) 
      (if (<= start stop) 
        (begin exps ... (forloop (+ start 1) stop exps ...))))))

There is no issue if I never call forloop (which I think is because it never has to expand the macro), so the code I have used to test these is:

(forloop 6 8 (display "g"))

What am I doing wrong? I have successfully implemented a when statement (on my own, but there are dozens of examples out there and I had already seen a lot of them). I think the recursive nature of what I want to do and the ellipsis are messing me up.

Millie Smith
  • 4,536
  • 2
  • 24
  • 60

1 Answers1

2

Macros are expanded at compile time, thus something like (begin exps ... (forloop (+ start 1) stop exps ...)) will expand forloop again and again, regardless of what the value of (+ start 1) is (which is evaluated at runtime).

Perhaps the best you can do, at least with syntax-rules, is to use the macro to capture only the expressions to run, and use non-macro code to deal with the looping:

(define-syntax forloop
  (syntax-rules ()
    ((forloop start stop exps ...)
     (let ((j stop))
       (let loop ((i start))
         (when (<= i j)
           exps ...
           (loop (+ i 1))))))))

You can also use a do loop:

(define-syntax forloop
  (syntax-rules ()
    ((forloop start stop exps ...)
     (let ((j stop))
       (do ((i start (+ i 1)))
           ((> i j))
         exps ...)))))
C. K. Young
  • 219,335
  • 46
  • 382
  • 435
  • Thanks, that works, and the compile time vs. run time makes sense, but I'm failing to see how your first example is fundamentally different than my first example... And yours works and mine doesn't lol. – Millie Smith Dec 28 '13 at 04:24
  • The fundamental difference is that `loop` in my version is not a macro. – C. K. Young Dec 28 '13 at 04:25
  • Is the letrec creating the macro? Or maybe the lambda? I didn't think it was a macro. – Millie Smith Dec 28 '13 at 04:28
  • Oops, I was looking at your last example, not the first one. The fundamental difference with your first example is that I'm not passing the expressions into the recursive calls. – C. K. Young Dec 28 '13 at 04:32
  • Hm.. Why is it wrong to pass the expressions into the recursive calls? Those are known at compile time. – Millie Smith Dec 28 '13 at 04:39
  • But you're right. I was able to fix my code by not passing the expressions into the recursive calls. – Millie Smith Dec 28 '13 at 04:41
  • Did you see my question? @ChrisJester-Young – Millie Smith Dec 28 '13 at 05:00
  • 1
    @MillieSmith your first code turns `(forloop a b c)` into `(letrec ((aloop (lambda(x y z) (if (<= x y) (begin z (aloop (+ x 1) y z)))))) (aloop a b c))`. So `c` is evaluated *once*, and its *value* is passed into the `aloop` call which does nothing with it. Your `aloop` is just a function - and so gets its arguments *values*; Chris's macro creates a named let construct into which your expressions are placed, as *expressions*. – Will Ness Dec 28 '13 at 09:53