deprecated
deprecated

Reputation: 5242

Why isn't let* the default let?

As probably all experienced elispers have found at some point, code like is broken:

(let ((a 3)
      (b 4)
      (c (+ a b)))
  c)

One should use the let* form instead when referring to a just-binded variable within the binding clauses.

I just wonder - why is a seemingly wrong behavior the default? Are there any risks on choosing always let* regardless of how is one gonna use it?

Upvotes: 5

Views: 870

Answers (4)

dkim
dkim

Reputation: 3970

Any let* forms can be easily (and mechanically) expressed using let. For example, if we didn't have the let* special form, we could express

(let* ((a 3)
       (b 4)
       (c (+ a b)))
  c)

as:

(let ((a 3))
  (let ((b 4))
    (let ((c (+ a b)))
      c)))

On the other hand, some let forms are quite difficult, if possible at all, to express using let*. The following let form cannot be expressed using let* without introducing an additional temporary variable or esoteric bitwise manipulation.

(let ((x y) (y x)) ; swap x and y
  ...)

So, in this sense, I think let is more fundamental than let*.

Upvotes: 23

joao
joao

Reputation: 3566

I was taught that the reason is mainly historical, but it might still hold: Since let does parallel assigment, having no data dependencies between the variables might give the compiler flexibility compile for speed or space and compile to something much faster than let*. I don't know how much the elisp compiler uses this.

A bit of googling reveals a similar question for Common Lisp: LET versus LET* in Common Lisp

Upvotes: 6

alinsoar
alinsoar

Reputation: 15783

it is incorrect to do so

(let ((a 10)
      (b a)) <- error
  b)

it is correct to do so:

(let* ((a 10)
       (b a))
   b) <- will evaluate to 10.

The error in the first case is due to semantics of let.

let* adds new symbols to the existing environment, evaluating them inside it.

let creates a new environment, evaluating the new symbols in the current envinronemnt, and the new environment will be disponible at the end of the evaluation of all new symbols, to evaluate the code of (let () code ).

let* is syntactic sugar for

(let ((a xxx))
  (let ((b a)) ...)

while let is syntactic sugar for

((lambda (a)
  ...)
  val_a)

The second form is much more common, so perhaps they thought to give it a shorter name...

Upvotes: 4

Jason Morgan
Jason Morgan

Reputation: 2330

I'm not sure I would call let broken. For instance, you may for whatever reason want to shadow a and b in the enclosed environment, while defining c with respect to the enclosing expression.

(let ((a 11)
      (b 12))
  (let ((a 3)
        (b 4)
        (c (+ a b)))
    ;; do stuff here will shadowed `a' and `b'
    c))
> 23

As for risks, I don't know if there are any per se. But why created multiple nested environments when you don't have to? There may be a performance penalty for doing so (I don't have enough experience with elisp to say).

Upvotes: 2

Related Questions