Recently , I just encountered the following codes:
(let* (line hash-table-for-x array2 hash-table-for-y array1)
...........
It is really strange to me since I thought that statement following
let* should be in (var value) pair, such as:
(let* (var1 value1)
(var2 value2)
........
So, is there any special use of let* ?
Thanks!
Jim
Jim Cheng wrote:
....
> It is really strange to me since I thought that statement following
> let* should be in (var value) pair, such as:
> (let* (var1 value1)
> (var2 value2)
> ........
Close, it should look like
(let* ( (var1 value1)
(var2 value2 ) )
..body .. )
Else how would you distinguish between the let's body start
and the variables declaration's end?
What you observed is the short cut for intializing varaibles with
NIL. The wisdom of initializing a variable that is to be bound
to a hashtable to NIL might not be a good idea depending upon
what the rest of the code does.
The language legalese...
http://www.xanalys.com/software_tools/reference/HyperSpec/Body/speope_letcm_letst.html
Lyman
"Jim Cheng" <·······@yahoo.com> wrote in message
······················@yahoo.com...
> Recently , I just encountered the following codes:
>
> (let* (line hash-table-for-x array2 hash-table-for-y array1)
> ...........
>
This would be an unnecessary use of let*.
Both let and let* can be given just a variable name rather than a list of
name and value as elements in the let list. The initial binding will then
be nil.
(let (a b (c 'foo))) is equivalent to (let ((a nil) (b nil) (c 'foo)))
Which brings to mind a related question:
I asked a junior programmer at work why he was using let* when he did not
use any of the new variables in subsequent bindings. He replied that it is
faster than let in Allegro 5.x
I did one test and it seems to be true:
(let ((start (get-internal-real-time)))
(dotimes (i 100000)
(let ((a 1) (b 2))))
(- (get-internal-real-time) start))
==>7200
(let ((start (get-internal-real-time)))
(dotimes (i 100000)
(let* ((a 1) (b 2))))
(- (get-internal-real-time) start))
==>6420
Is this not a bit surprising?
Coby
Coby Beck wrote:
> I did one test and it seems to be true:
>
> (let ((start (get-internal-real-time)))
> (dotimes (i 100000)
> (let ((a 1) (b 2))))
> (- (get-internal-real-time) start))
> ==>7200
> (let ((start (get-internal-real-time)))
> (dotimes (i 100000)
> (let* ((a 1) (b 2))))
> (- (get-internal-real-time) start))
> ==>6420
>
> Is this not a bit surprising?
Speed of the interpreted forms is not very
interesting.
Paul
In case no one has mentioned it, though
(let ((x nil)) ...)
means the same as
(let (x) ...)
the normal style convention I and others adhere to is that any use of
(let (x) ...)
really implies a promise not to use X until it's been explicitly assigned,
and so the NIL that is done in the second case might as well be "uninitialized"
but for the fact that the GC wouldn't like that.
On Sun, 27 Aug 2000 23:12:56 -0700, Lyman Taylor
<············@mindspring.com> wrote:
>"Paul F. Dietz" wrote:
>>
>> Coby Beck wrote:
>...
>> > Is this not a bit surprising?
>
> Not when pausing to think that the "parallel binding" semantics
> probably incur some overhead to compute.
>
> ...
> The following disassemble into essentially the same code.
>...
This is an interesting discussion to me. I have been preferring LET*
over LET in my own code, largely I think because I had this feeling
that it would perform better. I figured that LET would have to store
the new values temporarily before assigning the results. In fact I was
wrong. On the compiler I use, the execution of LET and LET* is
basically identical, with identical timings. I realize why now: the
compiler doesn't have to do any flow analysis, optimization or
anything like that. It simply assigns the variables as their values
are computed. The only difference is whether the variable names get
added to the name space of the compilation environment after all the
initialization code is run (in the case of LET) or one name at a time
(in the case of LET*). The thing is, this is done at compile time, and
has no effect whatsoever on the generated code (of course I mean if
there are no references to either of the names in the initialization
code). I should have realized this sooner, since I wrote the
compiler... :-)
Roger Corman
·····@corman.net
"Paul F. Dietz" <·····@interaccess.com> wrote in message
······················@interaccess.com...
> Speed of the interpreted forms is not very
> interesting.
Good point. So i did:
(defun test-let()
(let ((start (get-internal-real-time)))
(dotimes (i 50000000)
(let ((a 1) (b 2))
(+ a b)))
(- (get-internal-real-time) start)))
(defun test-let*()
(let ((start (get-internal-real-time)))
(dotimes (i 50000000)
(let* ((a 1) (b 2))
(+ a b)))
(- (get-internal-real-time) start)))
;;; Compiling file C:\Program Files\acl501\junk.lsp
;;; Writing fasl file C:\Program Files\acl501\junk.fasl
Warning: No IN-PACKAGE form seen in C:\Program Files\acl501\junk.lsp.
(Allegro Presto will be ineffective when loading a file having no IN-PACKAGE
form.)
;;; Fasl write complete
; Fast loading C:\Program Files\acl501\junk.fasl
> (test-let)
7630
> (test-let)
7580
> (test-let*)
6430
> (test-let*)
6420
> (test-let)
7630
> (test-let)
7690
> (test-let*)
6370
> (test-let*)
6430
>
Not likely to be the bottle neck in any application, but still seems
surprising....
Coby
Coby Beck wrote:
> "Paul F. Dietz" <·····@interaccess.com> wrote in message
> ······················@interaccess.com...
> > Speed of the interpreted forms is not very
> > interesting.
>
> Good point. So i did:
>
> (defun test-let()
> (let ((start (get-internal-real-time)))
> (dotimes (i 50000000)
> (let ((a 1) (b 2))
> (+ a b)))
> (- (get-internal-real-time) start)))
>
> (defun test-let*()
> (let ((start (get-internal-real-time)))
> (dotimes (i 50000000)
> (let* ((a 1) (b 2))
> (+ a b)))
> (- (get-internal-real-time) start)))
>
> ;;; Compiling file C:\Program Files\acl501\junk.lsp
> ;;; Writing fasl file C:\Program Files\acl501\junk.fasl
> Warning: No IN-PACKAGE form seen in C:\Program Files\acl501\junk.lsp.
> (Allegro Presto will be ineffective when loading a file having no IN-PACKAGE
> form.)
> ;;; Fasl write complete
> ; Fast loading C:\Program Files\acl501\junk.fasl
>
> > (test-let)
> 7630
> > (test-let)
> 7580
> > (test-let*)
> 6430
> > (test-let*)
> 6420
> > (test-let)
> 7630
> > (test-let)
> 7690
> > (test-let*)
> 6370
> > (test-let*)
> 6430
> >
>
> Not likely to be the bottle neck in any application, but still seems
> surprising....
>
> Coby
I did the same test with cmu cl
and i guess the result will surprise you or maybe you'll find this more
"logical" (in the sens of Espen Vestre post)
;; test-let.lisp
(defun test-let()
(let ((start (get-internal-real-time)))
(dotimes (i 50000000)
(let ((a 1) (b 2))
(+ a b)))
(- (get-internal-real-time) start)))
(defun test-let*()
(let ((start (get-internal-real-time)))
(dotimes (i 50000000)
(let* ((a 1) (b 2))
(+ a b)))
(- (get-internal-real-time) start)))
* (compile-file "home:test-let.lisp")
Python version 1.0, VM version Intel x86 on 28 AUG 00 02:27:20 pm.
Compiling: /amd/motodashi/krakatoa/home/hatchond/test-let.lisp 28 AUG 00
02:27:00 pm
Converted TEST-LET.
Compiling DEFUN TEST-LET:
Converted TEST-LET*.
Compiling DEFUN TEST-LET*:
Byte Compiling Top-Level Form:
home:test-let.x86f written.
Compilation finished in 0:00:01.
#p"/amd/motodashi/krakatoa/home/hatchond/test-let.x86f"
NIL
NIL
* (load "home:test-let.x86f")
; Loading #p"/amd/motodashi/krakatoa/home/hatchond/test-let.x86f".
T
* (test-let)
28
* (test-let*)
29
* (test-let)
28
* (test-let*)
28
* (test-let)
28
* (test-let*)
28
* (test-let)
28
*
In article <·····················@news.bc.tac.net>, "Coby Beck"
<·····@mercury.bc.ca> wrote:
> faster than let in Allegro 5.x
>
> I did one test and it seems to be true:
>
> (let ((start (get-internal-real-time)))
> (dotimes (i 100000)
> (let ((a 1) (b 2))))
> (- (get-internal-real-time) start))
> ==>7200
> (let ((start (get-internal-real-time)))
> (dotimes (i 100000)
> (let* ((a 1) (b 2))))
> (- (get-internal-real-time) start))
> ==>6420
>
> Is this not a bit surprising?
Btw., what is the value of INTERNAL-TIME-UNITS-PER-SECOND ?
--
Rainer Joswig, Hamburg, Germany
Email: ·············@corporate-world.lisp.de
Web: http://corporate-world.lisp.de/
"Coby Beck" <·····@mercury.bc.ca> writes:
> I did one test and it seems to be true:
>
> (let ((start (get-internal-real-time)))
> (dotimes (i 100000)
> (let ((a 1) (b 2))))
> (- (get-internal-real-time) start))
> ==>7200
> (let ((start (get-internal-real-time)))
> (dotimes (i 100000)
> (let* ((a 1) (b 2))))
> (- (get-internal-real-time) start))
> ==>6420
>
> Is this not a bit surprising?
'get-internal-real-time' doesn't really tell you anything at all, your
computer might have been doing other things. get-internal-run-time
*might* be a bit better for such purposes (or use the time macro,
which gives you some more information).
As another poster pointed out, run times for intepreted forms are not
very interesting, and if you try to compile those loops, they'll
compile into noops and have nonmesurable runtime.
But if you look at the disassembly of two similar functions, you'll
see that simple uses of let and let* compiles into the same thing.
In order to stop the compiler from optimizing away everything, I've
placed a function call within the scope of the lets:
(defun lett ()
(let ((a 1) (b 2))
(foo a b)))
(defun lett* ()
(let* ((a 1) (b 2))
(foo a b)))
USER(142): (disassemble #'lett)
;; disassembly of #<Function LETT>
;; formals:
;; constant vector:
0: FOO
;; code start: #x48efbe4:
0: 9de3bf98 save %o6, #x-68, %o6
4: 80a0e000 cmp %g3, #x0
8: 95d02010 tg %g0, #x10
12: 81100001 taddcctv %g0, %g1, %g0
16: 90102004 mov #x4, %o0
20: c4076022 ld [%i5 + 34], %g2 ; FOO
24: 92102008 mov #x8, %o1
28: 9fc1200b jmpl %g4 + 11, %o7
32: 86182002 xor %g0, #x2, %g3
36: 81c7e008 jmp %i7 + 8
40: 91ea0000 restore %o0, %g0, %o0
USER(143): (disassemble #'lett*)
;; disassembly of #<Function LETT*>
;; formals:
;; constant vector:
0: FOO
;; code start: #x48efc6c:
0: 9de3bf98 save %o6, #x-68, %o6
4: 80a0e000 cmp %g3, #x0
8: 95d02010 tg %g0, #x10
12: 81100001 taddcctv %g0, %g1, %g0
16: 90102004 mov #x4, %o0
20: c4076022 ld [%i5 + 34], %g2 ; FOO
24: 92102008 mov #x8, %o1
28: 9fc1200b jmpl %g4 + 11, %o7
32: 86182002 xor %g0, #x2, %g3
36: 81c7e008 jmp %i7 + 8
40: 91ea0000 restore %o0, %g0, %o0
USER(144):
--
(espen)
Hi Coby,
You wrote:
>
> I asked a junior programmer at work why he was using let* when he
> did not use any of the new variables in subsequent bindings.
If you had asked certain senior programmers they may have responded
with the question "Why not?". let and let* differ in meaning only when
one of the new variables is used in a subsequent binding. In the case
at issue there is no difference, hence either is valid. In a Lisp
style guide I once saw the recommendation that one should pick one of
the two as the default for such situations and stick to it. Which one
to use is then just a matter of preference.
Joachim
--
work: ········@realtimeint.com (http://www.realtimeint.com)
private: ·······@kraut.bc.ca (http://www.kraut.bc.ca)
Joachim Achtzehnter <·······@kraut.bc.ca> writes:
> > I asked a junior programmer at work why he was using let* when he
> > did not use any of the new variables in subsequent bindings.
>
> If you had asked certain senior programmers they may have responded
> with the question "Why not?". let and let* differ in meaning only when
> one of the new variables is used in a subsequent binding. In the case
> at issue there is no difference, hence either is valid. In a Lisp
> style guide I once saw the recommendation that one should pick one of
> the two as the default for such situations and stick to it. Which one
> to use is then just a matter of preference.
Indeed. Dick Waters uses LET* by default and uses LET only to remind
himself that there is a funny relationship between the setups that
requires parallel binding. He claims this is more natural, and
moreover probably more efficient because a dumb compiler is not
required (for lack of having done the flow analysis to tell) to buffer
up all the return values from each expression on the stack before
assigning the locals.
I suspect it's purely a matter of our having made LET have the shorter
name that we prefer to think of it as the better default. Had the names
been swapped, or had LET* been called SET or something equally punchy,
I bet a lot more people would use it as the default.
The most important thing is, as Joachim here suggests, to subscribe to a
well-known default. Of course, having a community-wide sense of what the
default is doesn't hurt--and I think that's why some people push for everyone
to make LET the default. But as with most style rules, there just can't
be any absolutes. Only priorities to weigh. In the end, things like
programmer effectiveness have to weigh in, too.
It's more important to know why you do something a certain way than to
do it a certain right way, even a so-called "right" way, without knowing
the reason. Dictated, thoughtless behavior is not wisdom, it's dogma.
In article <···············@world.std.com>,
Kent M Pitman <······@world.std.com> wrote:
>I suspect it's purely a matter of our having made LET have the shorter
>name that we prefer to think of it as the better default. Had the names
>been swapped, or had LET* been called SET or something equally punchy,
>I bet a lot more people would use it as the default.
To me it's not just the shortness, but the fact that "*" seems like a kind
of "special indicator", suggesting "Watch out, something unusual is
happening here!". Like the way Scheme uses "!" for operators that modify
data in place. So it seems natural to me that the version without the
special warning should be the default. (I have the same philosophy in
other contexts -- if a car has a switch that changes a mode, such as the
Overdrive button, I assume that the manufacturer recommends the setting
that *doesn't* light up a dashboard indicator as the default).
--
Barry Margolin, ······@genuity.net
Genuity, Burlington, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
In article <·················@burlma1-snr2>,
Barry Margolin <······@genuity.net> wrote:
>In article <···············@world.std.com>,
>Kent M Pitman <······@world.std.com> wrote:
>>I suspect it's purely a matter of our having made LET have the shorter
>>name that we prefer to think of it as the better default. Had the names
>>been swapped, or had LET* been called SET or something equally punchy,
>>I bet a lot more people would use it as the default.
>
>To me it's not just the shortness, but the fact that "*" seems like a kind
>of "special indicator", suggesting "Watch out, something unusual is
>happening here!". Like the way Scheme uses "!" for operators that modify
>data in place. So it seems natural to me that the version without the
>special warning should be the default. (I have the same philosophy in
>other contexts -- if a car has a switch that changes a mode, such as the
>Overdrive button, I assume that the manufacturer recommends the setting
>that *doesn't* light up a dashboard indicator as the default).
"*" is simply a shorthand for plural (cf regexps),
rather than indicating any out-of-the-usual-ness.
let* is just many let's (where each of the let's
introduces only one variable).
--d
In article <············@news.gte.com>,
Dorai Sitaram <····@goldshoe.gte.com> wrote:
>In article <·················@burlma1-snr2>,
>Barry Margolin <······@genuity.net> wrote:
>>In article <···············@world.std.com>,
>>Kent M Pitman <······@world.std.com> wrote:
>>>I suspect it's purely a matter of our having made LET have the shorter
>>>name that we prefer to think of it as the better default. Had the names
>>>been swapped, or had LET* been called SET or something equally punchy,
>>>I bet a lot more people would use it as the default.
>>
>>To me it's not just the shortness, but the fact that "*" seems like a kind
>>of "special indicator", suggesting "Watch out, something unusual is
>>happening here!". Like the way Scheme uses "!" for operators that modify
>>data in place. So it seems natural to me that the version without the
>>special warning should be the default. (I have the same philosophy in
>>other contexts -- if a car has a switch that changes a mode, such as the
>>Overdrive button, I assume that the manufacturer recommends the setting
>>that *doesn't* light up a dashboard indicator as the default).
>
>"*" is simply a shorthand for plural (cf regexps),
>rather than indicating any out-of-the-usual-ness.
>let* is just many let's (where each of the let's
>introduces only one variable).
That may be the origin of the name, but it doesn't change the way it
appears. For comparison, the *name* naming convention for special
variables was almost certainly chosen because the *'s make the names stand
out.
--
Barry Margolin, ······@genuity.net
Genuity, Burlington, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
Barry Margolin <······@genuity.net> writes:
> That may be the origin of the name, but it doesn't change the way it
> appears. For comparison, the *name* naming convention for special
> variables was almost certainly chosen because the *'s make the names stand
> out.
Yeah, I think you're right that there is at least some degree of
reaction among people to the special character. This same
morality-emphasizing device is employed in Scheme to make people feel
guilt about using "set!" (i.e., setq).
* Dorai Sitaram wrote:
> "*" is simply a shorthand for plural (cf regexps),
> rather than indicating any out-of-the-usual-ness.
> let* is just many let's (where each of the let's
> introduces only one variable).
Hmmm. And DO* is many DOs?
In article <···············@tfeb.org>, Tim Bradshaw <···@tfeb.org> wrote:
>* Dorai Sitaram wrote:
>
>> "*" is simply a shorthand for plural (cf regexps),
>> rather than indicating any out-of-the-usual-ness.
>> let* is just many let's (where each of the let's
>> introduces only one variable).
>
>Hmmm. And DO* is many DOs?
No. The "*" in DO* implies have the same
macroexpansion as for DO except substitute
the LETs by LET*s. :-)
--d
* Coby Beck wrote:
> OK. How about if*? : )
it kind of ought to be the same as COND -- a multiway if. But
actually I think it's some weird Franz thing with then/else keywords?
--tim
Tim Bradshaw <···@tfeb.org> writes:
> * Dorai Sitaram wrote:
>
> > "*" is simply a shorthand for plural (cf regexps),
> > rather than indicating any out-of-the-usual-ness.
> > let* is just many let's (where each of the let's
> > introduces only one variable).
>
> Hmmm. And DO* is many DOs?
DO* is such a puzzle. I'd go farther than this. I'd say that DO* is
ill-named because the let : let* :: do : do* claim is pretty weak, depending
on the axis you cleave along. Certainly it's not a simple nesting of DO's
in the way that LET* is a nesting of LETs.
You can make the case that DO* is a LET* around a loop with a lot of SETQ's
in it. (Note that SETQ should be SETQ* and PSETQ should be SETQ. Heh...
Isn't consistency wonderful? Then DO* would be LET*+LOOP+SETQ* while DO
was LET+LOOP+SETQ.)
In article <···············@world.std.com>,
Kent M Pitman <······@world.std.com> wrote:
>Tim Bradshaw <···@tfeb.org> writes:
>
>> * Dorai Sitaram wrote:
>>
>> > "*" is simply a shorthand for plural (cf regexps),
>> > rather than indicating any out-of-the-usual-ness.
>> > let* is just many let's (where each of the let's
>> > introduces only one variable).
>>
>> Hmmm. And DO* is many DOs?
>
>DO* is such a puzzle. I'd go farther than this. I'd say that DO* is
>ill-named because the let : let* :: do : do* claim is pretty weak, depending
>on the axis you cleave along. Certainly it's not a simple nesting of DO's
>in the way that LET* is a nesting of LETs.
The original reason for the * in LET* may have been based on the Kleene
closure, but like many things in language the original reason is often
forgotten and the notation remains. Once LET* became entrenched as an
entity by itself, it was natural to create the analogy: DO* is to DO as
LET* is to LET -- one binds sequentially, the other binds in parallel.
>You can make the case that DO* is a LET* around a loop with a lot of SETQ's
>in it. (Note that SETQ should be SETQ* and PSETQ should be SETQ. Heh...
>Isn't consistency wonderful? Then DO* would be LET*+LOOP+SETQ* while DO
>was LET+LOOP+SETQ.)
Computer languages, like natural languages, are littered with
inconsistencies due to the vagaries of history. When SETQ was named there
was no PSETQ, nor was there even a LET, let alone LET* (in those days,
anonymous lambdas or PROG were used if you wanted local variables). There
may have been DO, but I doubt there was DO*. So there was nothing to be
consistent or inconsistent with.
Maybe when the Common Lisp developers decided to extend SETQ to allow
multiple assignments they should have renamed it to PSETQ* -- the
sequential version of PSETQ. :)
--
Barry Margolin, ······@genuity.net
Genuity, Burlington, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
From: Patrick A. O'Donnell
Subject: Re: Special use of let* ???
Date:
Message-ID: <rt3djjq5qr.fsf@ascent.com>
"Coby Beck" <·····@mercury.bc.ca> writes:
> I did one test and it seems to be true:
>
> (let ((start (get-internal-real-time)))
> (dotimes (i 100000)
> (let ((a 1) (b 2))))
> (- (get-internal-real-time) start))
> ==>7200
> (let ((start (get-internal-real-time)))
> (dotimes (i 100000)
> (let* ((a 1) (b 2))))
> (- (get-internal-real-time) start))
> ==>6420
>
> Is this not a bit surprising?
Not really. You're timing the interpreter.
Practically speaking, I tend to think of let* as the more useful
default, since it's semantics are what I need in the vast majority of
cases. However, my fingers were trained long, long ago to type the
seven characters of "(let ((" when I need locals, and they've proven
strongly resistant to retraining.
- Pat