Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

While Loop Macro in DrRacket

I am trying to create a macro for while loop in DrRacket. Here is what I wrote:

(require mzlib/defmacro)

(define-macro my-while
  (lambda (condition  body)
    (list 'local (list (list 'define (list 'while-loop)
                             (list 'if condition
                                   (list body (list 'while-loop))
                                   '(void))))
          '(while-loop))))


(define x 0)

(my-while (< x 10)
          (begin              
            (display x)
            (newline)
            (set! x (+ x 1))))

The output of this program is:

0
1
2
3
4
5
6
7
8
9
error:  procedure application: expected procedure, given: #<void>; arguments were: #<void>

Can someone help me with this? Why wouldn't this macro just terminate and return void. It seems that when the condition is not true, the system tries to apply the void as an argument to some procedure.

like image 548
Rajesh Bhat Avatar asked Dec 13 '22 01:12

Rajesh Bhat


2 Answers

Ouch:

  1. Using this style of while loop encourages excessive use of imperative programming.
  2. Using define-macro creates unhygienic macros, which is a nightmare in Scheme.

While I don't encourage writing an imperative-style loop macro, for your reference, here's a non-define-macro version of the same macro:

(define-syntax-rule (my-while condition body ...)
  (let loop ()
    (when condition
      body ...
      (loop))))

It uses syntax-rules, which creates hygienic macros, and is much, much easier to read than what you have.


Now, for the actual answer for your question, first, let's write your original macro out in a more readable way:

(define-macro my-while
  (lambda (condition body)
    `(local ((define (while-loop)
               (if ,condition
                   (,body (while-loop))
                   (void))))
       (while-loop))))

Once you write it out this way, you can see where the real problem is: in the (,body (while-loop)) line, which should instead have been (begin ,body (while-loop)).

like image 185
Chris Jester-Young Avatar answered Dec 20 '22 14:12

Chris Jester-Young


Why use a macro when a plain old function will do?

;; fun-while : (-> Boolean) (-> Any) -> Void
(define (fun-while condition body)
  (when (condition)
    (body)
    (fun-while condition body))

Of course, this requires you to pass in repeatable actions that can be called (this is why condition and body are surrounded with parens in the body of fun-while), so you do need a macro if you want prettier syntax. But once you have a function that has the desired behavior, putting some sugar on top is trivial for this case:

(define-syntax-rule (my-while condition body ...)
   (fun-while (lambda () condition)
              (lambda () body ...)))

Now, as has been said, this encourages imperative style, which is frowned upon. Instead of mutation, try making the state explicit instead:

;; pure-while : forall State.
;;    (State -> Boolean)   ; the "condition" that inspects the state
;;    (State -> State)     ; the "body" that moves from one state to the next
;;    ->   ; curried
;;    State                ; the current state
;; -> State                ; produces the ending state
(define ((pure-while condition make-next) current-state)
   (if (condition current-state)
       (pure-while condition make-next (make-next current-state))
       current-state))

You'll notice that the first two arguments are now functions from State to something, and the result of applying to 2 arguments is also a function from State -> State. This is a recurring pattern that, as a Haskeller, I'd call the "State Monad". Discussion of putting sugar on top of this concept is a little beyond the scope of this conversation, though, so I'll just stop there.

like image 32
Dan Burton Avatar answered Dec 20 '22 12:12

Dan Burton