Reputation: 5242
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
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
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
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
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