In computer science, hygienic macros are macros whose expansion is guaranteed not to cause the accidental capture of identifiers. They are a feature of programming languages such as Scheme, Dylan, Rust, Nim, and Julia. The general problem of accidental capture was well known in the Lisp community before the introduction of hygienic macros. Macro writers would use language features that would generate unique identifiers (e.g., gensym) or use obfuscated identifiers to avoid the problem. Hygienic macros are a programmatic solution to the capture problem that is integrated into the macro expander. The term "hygiene" was coined in Kohlbecker et al.'s 1986 paper that introduced hygienic macro expansion, inspired by terminology used in mathematics.
The hygiene problem
Variable shadowing
In programming languages that have non-hygienic macro systems, it is possible for existing variable bindings to be hidden from a macro by variable bindings that are created during its expansion. In C, this problem can be illustrated by the following fragment:
#define INCI(i) { int a=0; ++i; }
int main(void) { int a = 4, b = 8; INCI(a); INCI(b); printf("a is now %d, b is now %d\n", a, b); return 0; }
Running the above through the C preprocessor produces:
int main(void) { int a = 4, b = 8; { int a = 0; ++a; }; { int a = 0; ++b; }; printf("a is now %d, b is now %d\n", a, b); return 0; }
The variable a declared in the top scope is shadowed by the a variable in the macro, which introduces a new scope. As a result, a is never altered by the execution of the program, as the output of the compiled program shows:
a is now 4, b is now 9
Standard library function redefinition
The hygiene problem can extend beyond variable bindings. Consider this Common Lisp macro:
(defmacro my-unless (condition &body body) `(if (not ,condition) (progn ,@body)))
While there are no references to variables in this macro, it assumes the symbols "if", "not", and "progn" are all bound to their usual definitions in the standard library. If, however the above macro is used in the following code:
(flet ((not (x) x)) (my-unless t (format t "This should not be printed!")))
The definition of "not" has been locally altered and so the expansion of my-unless changes.
Program-defined function redefinition
Of course, the problem can occur for program-defined functions in a similar way:
(defun user-defined-operator (cond) (not cond))
(defmacro my-unless (condition &body body) `(if (user-defined-operator ,condition) (progn ,@body)))
; ... later ...
(flet ((user-defined-operator (x) x)) (my-unless t (format t "This should not be printed!")))
The use site redefines user-defined-operator and hence changes the behavior of the macro.
Strategies used in languages that lack hygienic macros
The hygiene problem can be resolved with conventional macros using several alternative solutions.
Obfuscation
The simplest solution, if temporary storage is needed during macro expansion, is to use unusual variables names in the macro in hope that the same names will never be used by the rest of the program.
#define INCI(i) { int INCIa = 0; ++i; }
int main(void) { int a = 4, b = 8; INCI(a); INCI(b); printf("a is now %d, b is now %d\n", a, b); return 0; }
Until a variable named INCIa is created, this solution produces the correct output:
a is now 5, b is now 9
The problem is solved for the current program, but this solution is not robust. The variables used inside the macro and those in the rest of the program have to be kept in sync by the programmer. Specifically, using the macro INCI on a variable INCIa is going to fail in the same way that the original macro failed on a variable a.
Temporary symbol creation
In some programming languages, it is possible for a new variable name, or symbol, to be generated and bound to a temporary location. The language processing system ensures that this never clashes with another name or location in the execution environment. The responsibility for choosing to use this feature within the body of a macro definition is left to the programmer. This method was used in MacLisp, where a function named gensym could be used to generate a new symbol name. Similar functions (usually named gensym as well) exist in many Lisp-like languages, including the widely implemented Common Lisp standard and Elisp.