Prev Up Next

A two-argument disjunction form, my-or, could be defined as follows:

(define-macro my-or
  (lambda (x y)
    `(if ,x ,x ,y)))

my-or takes two arguments and returns the value of the first of them that is true (ie, non-#f). In particular, the second argument is evaluated only if the first turns out to be false.

(my-or 1 2)
=> 1

(my-or #f 2)
=> 2

There is a problem with the my-or macro as it is written. It re-evaluates the first argument if it is true: once in the if-test, and once again in the ``then'' branch. This can cause undesired behavior if the first argument were to contain side-effects, eg,

(my-or
  (begin 
    (display "doing first argument")
     (newline)
     #t)
  2)

displays "doing first argument" twice.

This can be avoided by storing the if-test result in a local variable:

(define-macro my-or
  (lambda (x y)
    `(let ((temp ,x))
       (if temp temp ,y))))

This is almost OK, except in the case where the second argument happens to contain the same identifier temp as used in the macro definition. Eg,

(define temp 3)

(my-or #f temp)
=> #f

Surely it should be 3! The fiasco happens because the macro uses a local variable temp to store the value of the first argument (#f) and the variable temp in the second argument got captured by the temp introduced by the macro.

To avoid this, we need to be careful in choosing local variables inside macro definitions. We could choose outlandish names for such variables and hope fervently that nobody else comes up with them. Eg,

(define-macro my-or
  (lambda (x y)
    `(let ((+temp ,x))
       (if +temp +temp ,y))))

This will work given the tacit understanding that +temp will not be used by code outside the macro. This is of course an understanding waiting to be disillusioned.

A more reliable, if verbose, approach is to use generated symbols that are guaranteed not to be obtainable by other means. The procedure gensym generates unique symbols each time it is called. Here is a safe definition for my-or using gensym:

(define-macro my-or
  (lambda (x y)
    (let ((temp (gensym)))
      `(let ((,temp ,x))
         (if ,temp ,temp ,y)))))

In the macros defined in this document, in order to be concise, we will not use the gensym approach. Instead, we will consider the point about variable capture as having been made, and go ahead with the less cluttered +-as-prefix approach. We will leave it to the astute reader to remember to convert these +-identifiers into gensyms in the manner outlined above.

Prev Up Next

Log in or register to write something here or to contact authors.