; Copyright (c) 2007 Oak Circle Software, Inc. ; ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to deal ; in the Software without restriction, including without limitation the rights ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ; copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; The above copyright notice and this permission notice shall be included in ; all copies or substantial portions of the Software. ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ; THE SOFTWARE. ;;; This is meant to be the final version of gensym-let, barring any problems ;;; discovered later. It includes full error-checking and some other ;;; improvements over the initial version. (defmacro gensym-let (vars-and-values &body body) "Creates a gensym in place of each variable listed in the vars-and-values list, replacing all instances of that variable in the body with the generated symbol, and optionally assigning it a value. Meant for use in other macros. The syntax is identical to that of let." (labels ((first-or-only (x) (if (listp x) (if (<= 1 (length x) 2) (the symbol (first x)) (error "The GENSYM-LET binding spec ~a is malformed." x)) (the symbol x))) (second-or-nil (x) (if (listp x) (second x) nil))) (let ((newlets nil)) (dolist (i vars-and-values `(let ,newlets ,@body)) (let ((g (gensym (concatenate 'string (symbol-name (first-or-only i)) "-")))) (unless (eql (second-or-nil i) nil) (setf newlets (append newlets `((,g ,(second-or-nil i)))))) (setf body (subst g (first-or-only i) body))))))) ;;; The following three functions were adapted from Paul Graham's book _ANSI ;;; Common Lisp_ (p166). They show why gensym-let is needed, and confirm that ;;; it eliminates the variable-capture problem as it's intended to. (defmacro ntimes (n &rest body) "A test macro to show why gensym-let is needed." `(do ((x 0 (+ x 1))) ((>= x ,n)) ,@body)) (defun use-ntimes-bad () "This code should return 15, but due to variable capture, it returns 10 instead." (let ((x 10)) (ntimes 5 (setf x (+ x 1))) x)) (defun use-ntimes-good () "Using gensym-let instead of a simple let completely avoids the variable capture. The code works the same way, but comes up with the right answer, 15." (gensym-let ((x 10)) (ntimes 5 (setf x (+ x 1))) x))