I understand the difference between LET and LET* (parallel versus sequential binding), and as a theoretical matter it makes perfect sense. But is there any case where you've ever actually needed LET? In all of my Lisp code that I've looked at recently, you could replace every LET with LET* with no change.
Edit: OK, I understand why some guy invented LET*, presumably as a macro, way back when. My question is, given that LET* exists, is there a reason for LET to stay around? Have you written any actual Lisp code where a LET* would not work as well as a plain LET?
I don't buy the efficiency argument. First, recognizing cases where LET* can be compiled into something as efficient as LET just doesn't seem that hard. Second, there are lots of things in the CL spec that simply don't seem like they were designed around efficiency at all. (When's the last time you saw a LOOP with type declarations? Those are so hard to figure out I've never seen them used.) Before Dick Gabriel's benchmarks of the late 1980's, CL was downright slow.
It looks like this is another case of backwards compatibility: wisely, nobody wanted to risk breaking something as fundamental as LET. Which was my hunch, but it's comforting to hear that nobody has a stupidly-simple case I was missing where LET made a bunch of things ridiculously easier than LET*.
LET
itself is not a real primitive in a Functional Programming Language, since it can replaced with LAMBDA
. Like this:
(let ((a1 b1) (a2 b2) ... (an bn))
(some-code a1 a2 ... an))
is similar to
((lambda (a1 a2 ... an)
(some-code a1 a2 ... an))
b1 b2 ... bn)
But
(let* ((a1 b1) (a2 b2) ... (an bn))
(some-code a1 a2 ... an))
is similar to
((lambda (a1)
((lambda (a2)
...
((lambda (an)
(some-code a1 a2 ... an))
bn))
b2))
b1)
You can imagine which is the simpler thing. LET
and not LET*
.
LET
makes code understanding easier. One sees a bunch of bindings and one can read each binding individually without the need to understand the top-down/left-right flow of 'effects' (rebindings). Using LET*
signals to the programmer (the one that reads code) that the bindings are not independent, but there is some kind of top-down flow - which complicates things.
Common Lisp has the rule that the values for the bindings in LET
are computed left to right. Just how the values for a function call are evaluated - left to right. So, LET
is the conceptually simpler statement and it should be used by default.
Types in LOOP
? Are used quite often. There are some primitive forms of type declaration that are easy to remember. Example:
(LOOP FOR i FIXNUM BELOW (TRUNCATE n 2) do (something i))
Above declares the variable i
to be a fixnum
.
Richard P. Gabriel published his book on Lisp benchmarks in 1985 and at that time these benchmarks were also used with non-CL Lisps. Common Lisp itself was brand new in 1985 - the CLtL1 book which described the language had just been published in 1984. No wonder the implementations were not very optimized at that time. The optimizations implemented were basically the same (or less) that the implementations before had (like MacLisp).
But for LET
vs. LET*
the main difference is that code using LET
is easier to understand for humans, since the binding clauses are independent of each other - especially since it is bad style to take advantage of the left to right evaluation (not setting variables as a side effect).