From: R. Bharat Rao
Subject: On let, &aux, and let*
Date: 
Message-ID: <bharat.683223819@milton>
Suppose I have to define a function foo with arguments arg1, arg2,
..., which requires several local variables, in1, ind2 ... & dep0, dep1,
dep2, ...

such that ind[i] = some function of arg1, arg2 ....
and 	dep0 = some function of arg1, arg2 .. & ind1, ind2 ...
	dep1 = f[arg1, arg2 .. ind1, ind2, .. *AND* dep0]
	dep2 = f[arg1, arg2 .. ind1, ind2, .. *AND* dep0, dep1]
	dep3 = f[arg1, arg2 .. ind1, ind2, .. *AND* dep0, dep1, dep2] ..

There are several alternate ways to do this.  Of the ones that follow,
which would be considered good programming practice, most readable,
and which would be most efficient.

1. A let and a let*

(defun foo  (arg1 arg2 ...)
  (let ((ind1 (f1 arg1 arg2 ...))
	(ind2 (f2 arg1 arg2 ...))
	(ind3 (f3 arg1 arg2 ...)))
    (let* ((dep0 (g0 arg1 arg2 .. ind1 ind2 ..))
	   (dep1 (g1 arg1 arg2 .. ind1 ind2 .. dep0))
	   (dep2 (g2 arg1 arg2 .. ind1 ind2 .. dep0 dep1)))
      (many computations))))

2. Using a single let*

(defun foo  (arg1 arg2 ...)
  (let* ((ind1 (f1 arg1 arg2 ...))
	 (ind2 (f2 arg1 arg2 ...))
	 (ind3 (f3 arg1 arg2 ...))
	 (dep0 (g0 arg1 arg2 .. ind1 ind2 ..))
	 (dep1 (g1 arg1 arg2 .. ind1 ind2 .. dep0))
	 (dep2 (g2 arg1 arg2 .. ind1 ind2 .. dep0 dep1)))
      (many computations)))

3. Using a single let

(defun foo  (arg1 arg2 ...)
  (let ((ind1 (f1 arg1 arg2 ...))
	(ind2 (f2 arg1 arg2 ...))
	(ind3 (f3 arg1 arg2 ...))
	dep0 dep1 dep2)
    (setq dep0 (g0 arg1 arg2 .. ind1 ind2 ..))
    (setq dep1 (g1 arg1 arg2 .. ind1 ind2 .. dep0))
    (setq dep2 (g2 arg1 arg2 .. ind1 ind2 .. dep0 dep1))
    (many computations)))

4. Using &aux and either setqing all the locals afterwards or within
   the &aux instead (could become quite bulky).

Is this a non-issue?

Or merely a matter of personal style?

I actually avoid let* as much as I can, preferiing option 3.

-Bharat
--
R. Bharat Rao                         E-mail: ······@cs.uiuc.edu
Beckman Institute for Advanced Science and Technology
Electrical & Computer Engineering, University of Illinois, Urbana
Snail Mail: Beckman Institue, 405 N. Matthews, Urbana, IL 61801

From: Kevin Rodgers
Subject: Re: On let, &aux, and let*
Date: 
Message-ID: <1991Aug26.172049.27984@colorado.edu>
In article <················@milton> ······@herodotus.cs.uiuc.edu (R. Bharat Rao) writes:
>
>Suppose I have to define a function foo with arguments arg1, arg2,
>..., which requires several local variables, in1, ind2 ... & dep0, dep1,
>dep2, ...
>
>such that ind[i] = some function of arg1, arg2 ....
>and 	dep0 = some function of arg1, arg2 .. & ind1, ind2 ...
>	dep1 = f[arg1, arg2 .. ind1, ind2, .. *AND* dep0]
>	dep2 = f[arg1, arg2 .. ind1, ind2, .. *AND* dep0, dep1]
>	dep3 = f[arg1, arg2 .. ind1, ind2, .. *AND* dep0, dep1, dep2] ..
>
>There are several alternate ways to do this.  Of the ones that follow,
>which would be considered good programming practice, most readable,
>and which would be most efficient.
>
>1. A let and a let*
>   [code deleted]
>2. Using a single let*
>   [code deleted]
>3. Using a single let
>   [code deleted]
>4. Using &aux and either setqing all the locals afterwards or within
>   the &aux instead (could become quite bulky).

Only (1) makes explicit the dependencies of the local variables ind[i]
and dep[j] that you describe, so personally, I prefer it.  (4) is
especially bad because it incorrectly introduces new parameters to the
function foo.
-- 
Kevin Rodgers                           ········@cs.colorado.edu
Department of Computer Science          (303) 492-8425
University of Colorado			GO BUFFS!
Boulder CO 80309-0430
From: R. Bharat Rao
Subject: Re: On let, &aux, and let*
Date: 
Message-ID: <bharat.683229499@milton>
In <······················@colorado.edu> ········@tigger.Colorado.EDU (Kevin Rodgers) writes:
>In article <················@milton> ······@herodotus.cs.uiuc.edu (R. Bharat Rao) writes:
>>
>>Suppose I have to define a function foo with arguments arg1, arg2,
>>..., which requires several local variables, in1, ind2 ... & dep0, dep1,
>>dep2, ...
>>such that ind[i] = some function of arg1, arg2 ....
>>and 	dep0 = some function of arg1, arg2 .. & ind1, ind2 ...
>>	dep1 = f[arg1, arg2 .. ind1, ind2, .. *AND* dep0]

>>1. A let and a let*
>>2. Using a single let*
>>3. Using a single let
>>4. Using &aux and either setqing all the locals afterwards or within
>>   the &aux instead (could become quite bulky).

>Only (1) makes explicit the dependencies of the local variables ind[i]
>and dep[j] that you describe, so personally, I prefer it.  (4) is
>especially bad because it incorrectly introduces new parameters to the
>function foo.

OK, even if 1 is the most aesthetically pleasing, but thats only one
side of the coin.  What about the relative efficiencies - assume foo
will be exceuted several 1000 times during the course of the program.

>Kevin Rodgers                           ········@cs.colorado.edu

-Bharat
--
R. Bharat Rao                         E-mail: ······@cs.uiuc.edu
Beckman Institute for Advanced Science and Technology
Electrical & Computer Engineering, University of Illinois, Urbana
Snail Mail: Beckman Institue, 405 N. Matthews, Urbana, IL 61801
From: Simon Leinen
Subject: Re: On let, &aux, and let*
Date: 
Message-ID: <SIMON.91Aug27152748@liasun6.epfl.ch>
In article <················@milton> ······@herodotus.cs.uiuc.edu (R.
Bharat Rao) writes:

   OK, even if 1 is the most aesthetically pleasing, but thats only
   one side of the coin.  What about the relative efficiencies -
   assume foo will be exceuted several 1000 times during the course of
   the program.

Every decent compiler should compile all versions to the same code.
Why not run a benchmark?  I replaced f1,f2,f3,g0,g1,g2 and
many-computations by no-ops declared NOTINLINE; then timed 1000000
executions of each of the FOO-i functions.  Results for 3 different
Lisps below.  I replaced FOO-4 by a sequence of function calls to
measure function call overhead alone, which dominated the benchmark in
any case.  The remaining differences between the versions are very
small, with (1) being the fastest version in CMU CL (Python) and
Genera/Ivory, and (2) being the fastest version in Allegro 4.0.1.

I bet that Lucid, being a compiler more in the spirit of Python, also
compiles version (1) best, if there is any difference at all.
-- 
Simon.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; File Name:	  let-test.lisp
;;; Description:  Benchmark different combinations of LET/LET*/&AUX
;;; Author:	  Simon Leinen (·····@liasun6.epfl.ch)
;;; Date Created: 27-Aug-91
;;; RCS $Header$  
;;; RCS $Log$	  
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(in-package #-(or Allegro Genera) "CL-USER"
	    #+Allegro "COMMON-LISP-USER"
	    #+Genera "FUTURE-COMMON-LISP-USER")

#+Genera
(progn
  (proclaim '(notinline f1 f2 f3 g0 g1 g2 foo-1 foo-2 foo-3 many-computations))
  (proclaim '(optimize (speed 3) (safety 0))))

#-Genera
(progn
  (declaim (notinline f1 f2 f3 g0 g1 g2 foo-1 foo-2 foo-3 many-computations))
  (declaim (optimize (speed 3) (safety 0))))

;;; 1. A let and a let*

(defun foo-1  (arg1 arg2)
  (let ((ind1 (f1 arg1 arg2))
	(ind2 (f2 arg1 arg2))
	(ind3 (f3 arg1 arg2)))
    (let* ((dep0 (g0 arg1 arg2 ind1 ind2))
	   (dep1 (g1 arg1 arg2 ind1 ind2 dep0))
	   (dep2 (g2 arg1 arg2 ind1 ind2 dep0 dep1)))
      (many-computations ind1 ind2 ind3 dep0 dep1 dep2))))

;;; 2. Using a single let*

(defun foo-2  (arg1 arg2)
  (let* ((ind1 (f1 arg1 arg2))
	 (ind2 (f2 arg1 arg2))
	 (ind3 (f3 arg1 arg2))
	 (dep0 (g0 arg1 arg2 ind1 ind2))
	 (dep1 (g1 arg1 arg2 ind1 ind2 dep0))
	 (dep2 (g2 arg1 arg2 ind1 ind2 dep0 dep1)))
      (many-computations ind1 ind2 ind3 dep0 dep1 dep2)))

;;; 3. Using a single let

(defun foo-3  (arg1 arg2)
  (let ((ind1 (f1 arg1 arg2))
	(ind2 (f2 arg1 arg2))
	(ind3 (f3 arg1 arg2))
	dep0 dep1 dep2)
    (setq dep0 (g0 arg1 arg2 ind1 ind2))
    (setq dep1 (g1 arg1 arg2 ind1 ind2 dep0))
    (setq dep2 (g2 arg1 arg2 ind1 ind2 dep0 dep1))
    (many-computations ind1 ind2 ind3 dep0 dep1 dep2)))

(defun foo-4 (arg1 arg2)
  (progn (f1 arg1 arg2) (f2 arg1 arg2) (f3 arg1 arg2)
	 (g0 arg1 arg2 arg1 arg2 arg1)
	 (g1 arg1 arg2 arg1 arg2 arg1 arg2)
	 (g2 arg1 arg2 arg1 arg2 arg1 arg2 arg1)))

(defun test-1 ()
  (time (dotimes (i 1000000) (foo-1 1 2))))

(defun test-2 ()
  (time (dotimes (i 1000000) (foo-2 1 2))))

(defun test-3 ()
  (time (dotimes (i 1000000) (foo-3 1 2))))

(defun test-4 ()
  (time (dotimes (i 1000000) (foo-4 1 2))))

(defun f1 (&rest args) (declare (ignore args)) nil)
(defun f2 (&rest args) (declare (ignore args)) nil)
(defun f3 (&rest args) (declare (ignore args)) nil)
(defun g0 (&rest args) (declare (ignore args)) nil)
(defun g1 (&rest args) (declare (ignore args)) nil)
(defun g2 (&rest args) (declare (ignore args)) nil)
(defun many-computations (&rest args) (declare (ignore args)) nil)

#|
   And here are the results.  CMU CL and Allegro were timed on a Sun
   4/470, Genera 8.0.2 on a Symbolics XL1200S board.  Note that only
   CMU CL gets the CONSing numbers right.

CMU Common Lisp:

* (progn (test-1) (test-2) (test-3) (test-4))
Evaluation took:
      16.930 seconds of real time
      16.100 seconds of user run time
       0.060 seconds of system run time
       0     minor page faults and
       0     major page faults and
       0     swaps and
       0     bytes consed.
Evaluation took:
      17.050 seconds of real time
      16.100 seconds of user run time
       0.040 seconds of system run time
       0     minor page faults and
       0     major page faults and
       0     swaps and
       0     bytes consed.
Evaluation took:
      16.990 seconds of real time
      16.310 seconds of user run time
       0.040 seconds of system run time
       0     minor page faults and
       0     major page faults and
       0     swaps and
       0     bytes consed.
Evaluation took:
      14.720 seconds of real time
      14.040 seconds of user run time
       0.050 seconds of system run time
       0     minor page faults and
       0     major page faults and
       0     swaps and
       0     bytes consed.
NIL
* 

Allegro 4.0.1:

<cl> (progn (test-1) (test-2) (test-3) (test-4))
cpu time (non-gc) 6083 msec user, 33 msec system
cpu time (gc)     0 msec user, 0 msec system
cpu time (total)  6083 msec user, 33 msec system
real time  6340 msec
space allocation:
 -9 cons cells, 0 symbols, -96 other bytes,cpu time (non-gc) 6016 msec user, 0 msec system
cpu time (gc)     0 msec user, 0 msec system
cpu time (total)  6016 msec user, 0 msec system
real time  6410 msec
space allocation:
 -9 cons cells, 0 symbols, -96 other bytes,cpu time (non-gc) 6217 msec user, 17 msec system
cpu time (gc)     0 msec user, 0 msec system
cpu time (total)  6217 msec user, 17 msec system
real time  6380 msec
space allocation:
 -9 cons cells, 0 symbols, -96 other bytes,cpu time (non-gc) 5200 msec user, 33 msec system
cpu time (gc)     0 msec user, 0 msec system
cpu time (total)  5200 msec user, 33 msec system
real time  5480 msec
space allocation:
 -9 cons cells, 0 symbols, -96 other bytes,
NIL 
<cl> 

Genera 8.0.2:

   (progn (test-1) (test-2) (test-3) (test-4))
Evaluation of (DOTIMES (I 1000000)
                (FOO-1 1 2)) took 21.808214 seconds of elapsed time including:
  0.138 seconds processing sequence breaks,
  0.002 seconds in the storage system (including 0.000 seconds waiting for pages):
    0.000 seconds processing 0 page faults including 0 fetches,
    0.002 seconds in creating and destroying pages, and
    0.000 seconds in miscellaneous storage system tasks.
2,108 list, 1,672 structure words consed in WORKING-STORAGE-AREA.
92 list words consed in SHEET-AREA.
Evaluation of (DOTIMES (I 1000000)
                (FOO-2 1 2)) took 20.411066 seconds of elapsed time including:
  0.093 seconds processing sequence breaks,
  0.000 seconds in the storage system (including 0.000 seconds waiting for pages):
    0.000 seconds processing 0 page faults including 0 fetches,
    0.000 seconds in creating and destroying pages, and
    0.000 seconds in miscellaneous storage system tasks.
200 structure words consed in WORKING-STORAGE-AREA.
Evaluation of (DOTIMES (I 1000000)
                (FOO-3 1 2)) took 21.148411 seconds of elapsed time including:
  0.129 seconds processing sequence breaks,
  0.000 seconds in the storage system (including 0.000 seconds waiting for pages):
    0.000 seconds processing 0 page faults including 0 fetches,
    0.000 seconds in creating and destroying pages, and
    0.000 seconds in miscellaneous storage system tasks.
7 list, 210 structure words consed in WORKING-STORAGE-AREA.
Evaluation of (DOTIMES (I 1000000)
                (FOO-4 1 2)) took 18.832664 seconds of elapsed time including:
  0.109 seconds processing sequence breaks,
  0.002 seconds in the storage system (including 0.000 seconds waiting for pages):
    0.000 seconds processing 0 page faults including 0 fetches,
    0.002 seconds in creating and destroying pages, and
    0.000 seconds in miscellaneous storage system tasks.
2,208 list, 1,698 structure words consed in WORKING-STORAGE-AREA.
92 list words consed in SHEET-AREA.
NIL

|#
From: Dieter Esswein
Subject: Re: On let, &aux, and let*
Date: 
Message-ID: <DIETER.91Aug28163805@Schopenhauer.first.gmd.de>
In article <···················@liasun6.epfl.ch> ·····@liasun2.epfl.ch (Simon Leinen) writes:

   Every decent compiler should compile all versions to the same code.
   Why not run a benchmark?  I replaced f1,f2,f3,g0,g1,g2 and
   many-computations by no-ops declared NOTINLINE; then timed 1000000
   executions of each of the FOO-i functions.  Results for 3 different
   Lisps below.  I replaced FOO-4 by a sequence of function calls to
   measure function call overhead alone, which dominated the benchmark in
   any case.  The remaining differences between the versions are very
   small, with (1) being the fastest version in CMU CL (Python) and
   Genera/Ivory, and (2) being the fastest version in Allegro 4.0.1.

   (defun foo-4 (arg1 arg2)
     (progn (f1 arg1 arg2) (f2 arg1 arg2) (f3 arg1 arg2)
	    (g0 arg1 arg2 arg1 arg2 arg1)
	    (g1 arg1 arg2 arg1 arg2 arg1 arg2)
	    (g2 arg1 arg2 arg1 arg2 arg1 arg2 arg1)))

 
Shouldn't it  be changed to

(defun foo-4 (arg1 arg2)
  (progn (f1 arg1 arg2) (f2 arg1 arg2) (f3 arg1 arg2)
	 (g0 arg1 arg2 arg1 arg2 arg1)
	 (g1 arg1 arg2 arg1 arg2 arg1 arg2)
	 (g2 arg1 arg2 arg1 arg2 arg1 arg2 arg1)
         (many-computations arg1 arg2 arg1 arg2 arg1 arg2)))

to measure all function calls?

Thus, the overhead incurred by using lets is largely diminuished (though still 
existing if you look at the disassembled code).

It appears to me that this is mainly a problem of register allocation.

This discussion reminds me of a short program (included at the end of this
posting) which just copies one value from
variable to variable. A good register allocator should be able to eliminate
nearly all those copies (perhaps with the assistance of the data flow
analysis). 
If you want to smile a little bit (at least if you are using Allegro or Lucid
on a SPARC) compile and disassemble it. 
Is there a compiler out there which produces only necessary copies?

Here are the results using the modified version of foo-4 run on a SUN4/470 with
Allegro4.0.1 and Lucid4.0: 

Allegro:

(progn (test-1) (test-2) (test-3) (test-4))
> cpu time (non-gc) 5900 msec user, 317 msec system
cpu time (gc)     0 msec user, 0 msec system
cpu time (total)  5900 msec user, 317 msec system
real time  7107 msec
space allocation:
 -9 cons cells, 0 symbols, -96 other bytes,cpu time (non-gc) 5983 msec user, 333 msec system
cpu time (gc)     0 msec user, 0 msec system
cpu time (total)  5983 msec user, 333 msec system
real time  7455 msec
space allocation:
 -9 cons cells, 0 symbols, -96 other bytes,cpu time (non-gc) 6183 msec user, 350 msec system
cpu time (gc)     0 msec user, 0 msec system
cpu time (total)  6183 msec user, 350 msec system
real time  8373 msec
space allocation:
 -9 cons cells, 0 symbols, -96 other bytes,cpu time (non-gc) 5267 msec user, 150 msec system
cpu time (gc)     0 msec user, 0 msec system
cpu time (total)  6033 msec user, 400 msec system
real time  8109 msec
space allocation:
 -9 cons cells, 0 symbols, -96 other bytes,
NIL 


Lucid:

(progn (test-1) (test-2) (test-3) (test-4))
Elapsed Real Time = 6.21 seconds
Total Run Time    = 5.99 seconds
User Run Time     = 5.95 seconds
System Run Time   = 0.04 seconds
Process Page Faults    =          0
Dynamic Bytes Consed   =          0
Ephemeral Bytes Consed =          0
Elapsed Real Time = 6.11 seconds
Total Run Time    = 6.02 seconds
User Run Time     = 5.99 seconds
System Run Time   = 0.03 seconds
Process Page Faults    =          0
Dynamic Bytes Consed   =          0
Ephemeral Bytes Consed =          0
Elapsed Real Time = 6.06 seconds
Total Run Time    = 5.91 seconds
User Run Time     = 5.88 seconds
System Run Time   = 0.03 seconds
Process Page Faults    =          0
Dynamic Bytes Consed   =          0
Ephemeral Bytes Consed =          0
Elapsed Real Time = 5.23 seconds
Total Run Time    = 5.12 seconds
User Run Time     = 5.12 seconds
System Run Time   = 0.00 seconds
Process Page Faults    =          0
Dynamic Bytes Consed   =          0
Ephemeral Bytes Consed =          0



And here comes the short register allocation test

(defun make-gensym-list (number)
  (mapcar #'gensym (make-list number :initial-element "std")))

;; Do not worry about the names, they are for historical reasons

(defmacro simulate-construct (name arguments variables)
  (let* ((std-variables (make-gensym-list (/ (length variables) 2))))
  `(defun ,name (test-val result ,@arguments)
    (let (,@variables ,@std-variables #| never used, optimize them out |#)
      ,@(mapcar 
	 #'(lambda (var) `(setq ,var test-val))
	 variables)
      ;; now use them
      ,@(mapcar
	 #'(lambda (var1 var2) `(setq ,var2 ,var1))
	 variables (reverse variables))
      ;; just use them again in a tail-recursive function
      (setq result (f1 ,@variables result))))))

(defun f1 (&rest x))

;;; and try for example that
(simulate-construct test-1 () (a b c d e f g h))
(simulate-construct test-2 () (a b c d e f g h i j k l m n o p))
(simulate-construct test-3 () (a1 a2 a3 a4 a5 a6 a7 a8 b1 b2 b3 b4 b5 b6 b7 b8 c1 c2 c3 c4 c5 c6 c7 c8 ))

--
ATTENTION: DO NOT REPLY TO THE ADDRESS IN THE HEADER!!
-------------------------------------------------------------------------------
Dieter Esswein                                   ·······@kmx.gmd.dbp.de  or
GMD FIRST 				             ······@gmdtub.uucp
Hardenbergplatz 2                              (German National Research Center
D-1000 Berlin 12, Germany                           for Computer Science)
From: Barry Margolin
Subject: Re: On let, &aux, and let*
Date: 
Message-ID: <1991Aug26.210240.22777@Think.COM>
In article <······················@colorado.edu> ········@tigger.Colorado.EDU (Kevin Rodgers) writes:
>In article <················@milton> ······@herodotus.cs.uiuc.edu (R. Bharat Rao) writes:
>>
>>Suppose I have to define a function foo with arguments arg1, arg2,
>>..., which requires several local variables, in1, ind2 ... & dep0, dep1,
>>dep2, ...
>>
>>such that ind[i] = some function of arg1, arg2 ....
>>and 	dep0 = some function of arg1, arg2 .. & ind1, ind2 ...
>>	dep1 = f[arg1, arg2 .. ind1, ind2, .. *AND* dep0]
>>	dep2 = f[arg1, arg2 .. ind1, ind2, .. *AND* dep0, dep1]
>>	dep3 = f[arg1, arg2 .. ind1, ind2, .. *AND* dep0, dep1, dep2] ..
>>
>>There are several alternate ways to do this.  Of the ones that follow,
>>which would be considered good programming practice, most readable,
>>and which would be most efficient.
>>
>>1. A let and a let*
>>   [code deleted]
>>2. Using a single let*
>>   [code deleted]
>>3. Using a single let
>>   [code deleted]
>>4. Using &aux and either setqing all the locals afterwards or within
>>   the &aux instead (could become quite bulky).
>
>Only (1) makes explicit the dependencies of the local variables ind[i]
>and dep[j] that you describe, so personally, I prefer it.  (4) is
>especially bad because it incorrectly introduces new parameters to the
>function foo.

I would consider all of them reasonable and readable.  I personally would
use (2), because I dislike using multiple binding forms when one will do,
just because of the increased indentation.  My rule is that I use LET if
all the bindings are independent, and LET* if there are any dependencies
between any of the variables.  A few weeks ago I received mail from someone
who said he always uses LET*, so that he doesn't get screwed if he adds a
dependency and forgets to change the LET to LET*.  If I were judging a
programming contest, I would probably pick (1) as the best, and admit
sheepishly that I don't use the "best" style in my personal coding.  I
sometimes use (3) when the interdependencies are complicated and/or
dynamic, so nested LETs would get messy.  I rarely use &AUX at all.  I
sometimes end up with code that looks like (3) or uses &AUX when I'm adding
a variable to an existing function; rather than restructure the function,
I'll just add the uninitialized variable to an existing LET, or as an &AUX
variable if the function doesn't already have a LET with a wide enough
scope, and then SETQ it later.

As for performance, the differences should be extremely minor, if there are
any.  The portable semantics of all those mechanisms are equivalent, so the
only differences in generated code should be due to implementation-specific
differences.  For instance, the compiler may be able to do better analysis
of some of them, in order to optimize the initialization forms.

Kevin Rodgers's complaint about (4) seems to be unfounded.  &AUX doesn't
introduce any new parameters to the function, it simply uses the lambda
list as a place to introduce local variables.  It's just a shorthand for
starting a function with an initial LET*.
-- 
Barry Margolin, Thinking Machines Corp.

······@think.com
{uunet,harvard}!think!barmar
From: Kevin Rodgers
Subject: Re: On let, &aux, and let*
Date: 
Message-ID: <1991Aug26.230224.12932@colorado.edu>
In article <······················@Think.COM> ······@think.com writes:
>Kevin Rodgers's complaint about (4) seems to be unfounded.  &AUX doesn't
>introduce any new parameters to the function, it simply uses the lambda
>list as a place to introduce local variables.  It's just a shorthand for
>starting a function with an initial LET*.

Yep, as I read through your followup I just realized my mistake.  I
never use &aux, and must have read it as &opt, which is my mental name
for &optional.
-- 
Kevin Rodgers                           ········@cs.colorado.edu
Department of Computer Science          (303) 492-8425
University of Colorado			GO BUFFS!
Boulder CO 80309-0430
From: Bob Kerns
Subject: Re: On let, &aux, and let*
Date: 
Message-ID: <1991Aug26.221704.22975@crl.dec.com>
In article <················@milton>, ······@herodotus.cs.uiuc.edu (R. Bharat Rao) writes:


> 1. A let and a let*
> 2. Using a single let*
> 3. Using a single let
> 4. Using &aux and either setqing all the locals afterwards or within
>    the &aux instead (could become quite bulky).

> Is this a non-issue?

Well, it's certainly not a major one, except I'd avoid #4 like
the plague.

> Or merely a matter of personal style?
> 
> I actually avoid let* as much as I can, preferiing option 3.

I actually avoid SETQ as much as I can, preferring option 2.
From: Jeff Dalton
Subject: Re: On let, &aux, and let*
Date: 
Message-ID: <5291@skye.ed.ac.uk>
In article <······················@crl.dec.com> ···@crl.dec.com writes:
>In article <················@milton>, ······@herodotus.cs.uiuc.edu (R. Bharat Rao) writes:
>
>
>> 1. A let and a let*
>> 2. Using a single let*
>> 3. Using a single let
>> 4. Using &aux and either setqing all the locals afterwards or within
>>    the &aux instead (could become quite bulky).

>> I actually avoid let* as much as I can, preferiing option 3.

>I actually avoid SETQ as much as I can, preferring option 2.

I tend to use (2) whenever some later variables depend on some earlier
ones.  I feel that in addition to the scoping difference between LET
and LET*, LET* emphasises seqential evaluation.  (This is clearer in
Scheme, where only LET* evaluates the init forms sequentially, but I
think it makes sense in CL too.)  So whenever I write code to do
several things in sequence, saving the results in variables, I tend
to use a LET*.

Some people feel that nested LETS are better when only some variables
depend on earlier ones, so as to show more exactly when dependencies
occur, but I think the result is often (though not always) harder to
read.

BTW, I think LET* is very useful for people who are just learning
Lisp, because it lets them "think sequentially" while nonetheless
avoiding assignment.  I wouldn't suggest using it exclusively,
though.  For some cases where I think it's helpful, look at the
Scheme code in Ravi Sethi's book on programming languages, for
example.

-- jd