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*.
The let expression is a special form in Lisp that you will need to use in most function definitions. let is used to attach or bind a symbol to a value in such a way that the Lisp interpreter will not confuse the variable with a variable of the same name that is not part of the function.
The left parenthesis before let matches the right parenthesis after the body, and together they define the code block within which the local variables are recognized. The body, as usual, is any sequence of LISP expressions. Let returns the value of the last expression evaluated in the body.
In Scheme, you can use local variables pretty much the way you do in most languages. When you enter a let expression, the let variables will be bound and initialized with values. When you exit the let expression, those bindings will disappear.
setf is actually a macro that examines an access form and produces a call to the corresponding update function. Given the existence of setf in Common Lisp, it is not necessary to have setq, rplaca, and set; they are redundant. They are retained in Common Lisp because of their historical importance in Lisp.
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).
You don't need LET, but you normally want it.
LET suggests that you're just doing standard parallel binding with nothing tricky going on. LET* induces restrictions on the compiler and suggests to the user that there's a reason that sequential bindings are needed. In terms of style, LET is better when you don't need the extra restrictions imposed by LET*.
It can be more efficient to use LET than LET* (depending on the compiler, optimizer, etc.):
(The above bullet points apply to Scheme, another LISP dialect. clisp may differ.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With