Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LET versus LET* in Common Lisp

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*.

like image 307
Ken Avatar asked Feb 16 '09 23:02

Ken


People also ask

What are LET and LET* forms in Lisp*?

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.

How does let work in LISP?

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.

What is let scheme?

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.

What does Setf do in LISP?

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.


2 Answers

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).

like image 55
Rainer Joswig Avatar answered Sep 28 '22 11:09

Rainer Joswig


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.):

  • parallel bindings can be executed in parallel (but I don't know if any LISP systems actually do this, and the init forms must still be executed sequentially)
  • parallel bindings create a single new environment (scope) for all the bindings. Sequential bindings create a new nested environment for every single binding. Parallel bindings use less memory and have faster variable lookup.

(The above bullet points apply to Scheme, another LISP dialect. clisp may differ.)

like image 33
Mr Fooz Avatar answered Sep 28 '22 11:09

Mr Fooz