Macros

Using gensym for Safer Macros

When writing macros, one must be careful to avoid capturing external variables or using variable names that might conflict with user code.

We will use an example macro nif (see http://letoverlambda.com/index.cl/guest/chap3.html#sec_5 for a more complete description.) nif is an example, something like a numeric if, where based on the expression, one of the 3 forms is called depending on if the expression is positive, zero or negative.

A first pass might be something like:

(defmacro nif [expr pos-form zero-form neg-form]
  `(do
    (setv obscure-name ~expr)
    (cond (> obscure-name 0) ~pos-form
          (= obscure-name 0) ~zero-form
          (< obscure-name 0) ~neg-form)))

where obscure-name is an attempt to pick some variable name as not to conflict with other code. But of course, while well-intentioned, this is no guarantee.

The method gensym is designed to generate a new, unique symbol for just such an occasion. A much better version of nif would be:

(defmacro nif [expr pos-form zero-form neg-form]
  (setv g (hy.gensym))
  `(do
     (setv ~g ~expr)
     (cond (> ~g 0) ~pos-form
           (= ~g 0) ~zero-form
           (< ~g 0) ~neg-form)))

This is an easy case, since there is only one symbol. But if there is a need for several gensym’s there is a second macro with-gensyms that basically expands to a setv form:

(with-gensyms [a b c]
  ...)

expands to:

(do
  (setv a (hy.gensym)
        b (hy.gensym)
        c (hy.gensym))
  ...)

so our re-written nif would look like:

(defmacro nif [expr pos-form zero-form neg-form]
  (with-gensyms [g]
    `(do
       (setv ~g ~expr)
       (cond (> ~g 0) ~pos-form
             (= ~g 0) ~zero-form
             (< ~g 0) ~neg-form))))

Finally, though we can make a new macro that does all this for us. defmacro/g! will take all symbols that begin with g! and automatically call gensym with the remainder of the symbol. So g!a would become (hy.gensym "a").

Our final version of nif, built with defmacro/g! becomes:

(defmacro/g! nif [expr pos-form zero-form neg-form]
  `(do
     (setv ~g!res ~expr)
     (cond (> ~g!res 0) ~pos-form
           (= ~g!res 0) ~zero-form
           (< ~g!res 0) ~neg-form)))