From: Klaus Weidner
Subject: Re: Python gets macros
Date: 
Message-ID: <Imdrd.51101$SM5.1796@news.easynews.com>
On 2004-11-30, Mark Carter <ยทยท@privacy.net> wrote:
> One thing I've wondered if it's possible to do, but I can't seem to find 
> the answer for, is a "localised let".
[...]
> but I wonder if one could write something like:
>
> (defun foo ()
> 	(local-let x 0)
> 	... do something with x)
>
> ,which looks neater to me.

Sure, for example by using a modified defun, called "defun+" in the code below.
It rewrites let, flet, labels, and macrolet forms to behave in a way more like
the type of variable definitions people are used to from other languages.

The definitions last until the end of the scope where you introduced them, and
you can continue using the "classic" form as well. Note that the expansion only
adds additional parentheses, it doesn't rearrange anything otherwise.

IMHO this does help make code look neater due to fewer distracting parentheses. 

-Klaus

(defun pairs (l)
  "Returns list of two-element lists built from successive elements of L.
Example: (pairs '(1 2 3 4 5)) => ((1 2) (3 4) (5 NIL))
"
  (let ((a (car l))
	(b (cadr l))
	(rest (cddr l)))
    (if rest
	(cons (list a b) (pairs rest))
	(list (list a b)))))

(defun fn-expand-let (f)
  "recursive macroexpander helper function for defun+
Example:
   (fn-expand-let '((print (let a 1) a) (let b 2) b (let ((c 3)) c)))
=> ((print
     (let ((a 1))
       a))
    (let ((b 2))
      b
      (let ((c 3))
        c)))

   (fn-expand-let '((flet a (x) (* 2 x)) (print (a arg))))
=> ((flet ((a (x)
          (* 2 x)))
     (print (a arg))))
"
  (cond ((not (consp f)) f) ;; atom

	;; Is it a (let a ... b ...) form?
	((and (consp (car f))
	      (eq 'let (caar f))
	      (not (consp (cadar f))))
	 ;; Yes, expand into (let ((a ...) (b ...)) rest)
	 `((let (,@(pairs (cdar f)))
	     ,@(fn-expand-let (cdr f)))))

	;; Is it flet/labels/macrolet ?
	((and (consp (car f))
	      (member (caar f) '(flet labels macrolet))
	      (not (consp (cadar f))))
	 ;; Yes, expand it. Only one is supported. Recurse on
	 ;; flet body as well so that (let a b) works in it.
	 `((,(caar f) ((,(cadar f) ,@(fn-expand-let (cddar f))))
	     ,@(fn-expand-let (cdr f)))))

	;; It's something else, recurse...
	(t (cons (fn-expand-let (car f))
		 (fn-expand-let (cdr f))))))

;; Note: don't use defun+ inside the file where it's defined unless you 
;; take care to make fn-expand-let available at macroexpansion time.
;; 
(defmacro defun+ (name args &body body)
  "Define functions with simplified local variable syntax (let var
value) and local function syntax (flet (args body)), the binding stays
in scope from the definition to the end of the lexical level:

  (... (let a b) ...) => (... (let ((a b)) ...))

Multiple variables may be declared as (let a 1 b 2), but don't overdo it.

Example:
  (defun+ foo () 
    (flet a (x) (sqrt x))
    (a 2))

  (foo) => 1.4142135

  (defun+ foo (x)
      (when (> x 4)
	(let y (1+ x)
	     z (* 2 x))
	(print (list y z)))
      ;; y and z now out of scope
      (print 'end))
"
  `(defun ,name ,args ,@(fn-expand-let body)))