Hygienic macro
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,[1] Dylan,[2] 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.[3] The hygiene problemVariable shadowingIn 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 is now 4, b is now 9 Standard library function redefinitionThe 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 Note however that for Common Lisp this behavior is forbidden, as per 11.1.2.1.2 Constraints on the COMMON-LISP Package for Conforming Programs. It is also possible to completely redefine functions anyway. Some implementations of Common Lisp provide Package Locks to prevent the user to change definitions in packages by mistake. Program-defined function redefinitionOf 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 Strategies used in languages that lack hygienic macrosThe hygiene problem can be resolved with conventional macros using several alternative solutions. ObfuscationThe 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 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 Temporary symbol creationIn 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 Although symbol creation solves the variable shadowing issue, it does not directly solve the issue of function redefinition.[5] However, Read-time uninterned symbolThis is similar to obfuscation in that a single name is shared by multiple expansions of the same macro. Unlike an unusual name, however, a read time uninterned symbol is used (denoted by the PackagesUsing packages such as in Common Lisp, the macro simply uses a private symbol from the package in which the macro is defined. The symbol will not accidentally occur in user code. User code would have to reach inside the package using the double colon ( For example, in the program-defined function redefinition example, the Literal objectsIn some languages the expansion of a macro does not need to correspond to textual code; rather than expanding to an expression containing the symbol Hygienic transformationHygienic macro systems in languages such as Scheme use a macro expansion process that preserves the lexical scoping of all identifiers and prevents accidental capture. This property is called referential transparency. In cases where capture is desired, some systems allow the programmer to explicitly violate the hygiene mechanisms of the macro system. For example, Scheme's (define-syntax my-unless
(syntax-rules ()
((_ condition body ...)
(if (not condition)
(begin body ...)))))
(let ((not (lambda (x) x)))
(my-unless #t
(display "This should not be printed!")
(newline)))
The hygienic macro processor responsible for transforming the patterns of the input form into an output form detects symbol clashes and resolves them by temporarily changing the names of symbols. The basic strategy is to identify bindings in the macro definition and replace those names with gensyms, and to identify free variables in the macro definition and make sure those names are looked up in the scope of the macro definition instead of the scope where the macro was used. ImplementationsMacro systems that automatically enforce hygiene originated with Scheme. The original KFFD algorithm for a hygienic macro system was presented by Kohlbecker in 1986.[3] At the time, no standard macro system was adopted by Scheme implementations. Shortly thereafter in 1987, Kohlbecker and Wand proposed a declarative pattern-based language for writing macros, which was the predecessor to the Syntax-rulesSyntax-rules is a high-level pattern matching facility that attempts to make macros easier to write. However, (define-syntax swap!
(syntax-rules ()
((_ a b)
(let ((temp a))
(set! a b)
(set! b temp)))))
Syntax-caseDue to the deficiencies of a purely (define-syntax swap!
(lambda (stx)
(syntax-case stx ()
((_ a b)
(syntax
(let ((temp a))
(set! a b)
(set! b temp)))))))
However, Other systemsOther macro systems have also been proposed and implemented for Scheme. Syntactic closures and explicit renaming[11] are two alternative macro systems. Both systems are lower-level than syntax-rules and leave the enforcement of hygiene to the macro writer. This differs from both syntax-rules and syntax-case, which automatically enforce hygiene by default. The swap examples from above are shown here using a syntactic closure and explicit renaming implementation respectively: ;; syntactic closures
(define-syntax swap!
(sc-macro-transformer
(lambda (form environment)
(let ((a (close-syntax (cadr form) environment))
(b (close-syntax (caddr form) environment)))
`(let ((temp ,a))
(set! ,a ,b)
(set! ,b temp))))))
;; explicit renaming
(define-syntax swap!
(er-macro-transformer
(lambda (form rename compare)
(let ((a (cadr form))
(b (caddr form))
(temp (rename 'temp)))
`(,(rename 'let) ((,temp ,a))
(,(rename 'set!) ,a ,b)
(,(rename 'set!) ,b ,temp))))))
Languages with hygienic macro systems
CriticismHygienic macros offer safety and referential transparency at the expense of making intentional variable capture less straight-forward. Doug Hoyte, author of Let Over Lambda, writes:[16]
— Doug Hoyte Many hygienic macro systems do offer escape hatches without compromising on the guarantees that hygiene provides; for instance, Racket allows you to define syntax parameters, which allow you to selectively introduce bound variables. Gregg Hendershott gives an example at Fear of Macros[17] of implementing an anaphoric if operator in this way. See alsoNotes
References
|
Portal di Ensiklopedia Dunia