Search code examples
macrosracketmetaprogramming

Why my transformation of this function into a Racket macro is generating an infinite recursion?


I am trying to understand Racket's macros. For that, I built this function for a specific purpose:

(define  (while-less-fun max)
  (cond [(< a (- max 1)) 
        (and (begin (set! a (+ a 1)) (print "x") a) (while-less-fun max))]
        [(< a max)
         (begin (set! a (+ a 1)) (print "x") a)]
        [(>= a max)
         (begin (set! a (+ a 1)) (print "x") a)]
        ))

It works as intended on the REPL:

> a
2

> (while-less-fun 7)
"x""x""x""x""x"7

> a
7

I tried to transform the function above in the macro bellow. My goal would be to generate the same results:

(define-syntax while-less
  (syntax-rules (do)
    [(while-less maximo do begin-exp)
       (cond [(< a (- maximo 1)) 
         (and begin-exp (while-less maximo do begin-exp))]
        [(< a maximo)
         begin-exp]
        [(>= a maximo)
         begin-exp])]))

The expected result was:

(define a 2)
(while-less 7 do (begin (set! a (+ a 1)) (print "x") a))
(while-less 7 do (begin (set! a (+ a 1)) (print "x") a))

Evaluating the second line would print "x" 5 times and change a to be 7. Also, evaluating the third line would print "x" 1 time and would change a to be 8.

Unfortunately, this generates an infinite recursion which really surprises me.

What did I do wrong? Why did this infinite recursion happen?

I know that you should avoid writing macros when a function is able to solve the problem. But, is my approach the wrong way of trying to accomplish this task?


Solution

  • In the first cond clause, you call "(while-less maximo do begin-exp)" with the same arguments, the entire body of the macro will be expanded again by calling the same "(while-less maximo do begin-exp)", causing the loop.

    What you can do is create a generic while for a condition:

    (define-syntax while 
      (syntax-rules (do)
        [(while condition do begin-exp)
         (let loop ()
           (when condition
             begin-exp
             (loop)))]))
    
    > (define a 2)
    
    > (while (< a 7) do (begin (set! a (+ a 1)) (print "x") a))
    "x""x""x""x""x"
    
    > (while (< a 7) do (begin (set! a (+ a 1)) (print "x") a))
    
    >