> And if it's executed conditionally, what does that mean? The var is declared on one branch but not the other?
Here is some C code:
if (cond()) {
preamble();
int a = 5;
do_stuff(a);
} else {
do_stuff(6);
}
And some Scheme code: (if (cond)
(begin
(preamble)
(define a 5)
(do-stuff a))
(do-stuff 6))
So why can't Common Lisp have the equivalent? (if (cond)
(progn
(preamble)
(var a 5)
(do-stuff a))
(do-stuff 6))
Instead, CL forces you to do this: (if (cond)
(progn
(preamble)
(let ((a 5))
(do-stuff a)))
(do-stuff 6))
Do you see the problem? Common Lisp wants you to declare every set of intermediate variables in a nested scope, leading to super-deep nesting unless you start breaking you function into small pieces for no reason (which then hurts readability).This is why languages die, when the old guard refuses to see that there are better ways of doing things than what they are used to.
(if (cond)
(progn
(preamble)
(let ((a 5))
(do-stuff a)))
(do-stuff 6))
Above would be unusual. I'd only write code that way if preamble was dependent on some previous binding of a. Note that let subsumes progn in most cases.
I'd write it like this: (if (cond)
(let ((a 5))
(preamble)
(do-stuff a))
(do-stuff 6))No. Explicit scoping is a huge plus.
> This is why languages die, when the old guard refuses to see that there are better ways of doing things than what they are used to.
Don't mistake this for not being able to see beyond your nose.
edit:
Let me also add this: you cannot get away from scoping. If you create intermediate values in your language of choice you better understand the implicit (and sometimes very complicated) scoping rules of that language. All that Common Lisp does is it makes this scoping explicit.
In my opinion, it is a giant, flaming misfeature.
> Which is to say, almost every language apart from Common Lisp
Algol; Ada; Modula 1, 2, and 3; Oberon; Eiffel; ...
Racket also allows it and encourages it in the style guide, using the "less nesting" (and thus shallower indentation ) rationale you bring up.
(if (cond)
(begin
(preamble)
(define a 5)
(do-stuff a))
(do-stuff 6))
This is not defining a local variable in Scheme. The various Scheme standards also require that DEFINE appears at the top of a body.> Common Lisp wants you to declare every set of intermediate variables in a nested scope
Lisp programmers edit code by list operations. For example, I can set the cursor between (preamble) and the (let ...) form. control-meta-t transposes the lists. The let form is moved upwards and the enclosed body is moved, too. Try that in the C code. Code transformations are vastly easier with explicit scopes.
JavaScript has introduced a LET for a reason: block scope is clearer than VAR (function / global scope).
(defun foo (a)
(if a
(var b a))
(print b)) ; what's the value here?
In Common Lisp above would not be valid.I would need to write:
(defun foo (a)
(let (b)
(if a
(setf b a))
(print b)))
I can then immediately see that each B is inside a LET scope, which defines it. It's just by simply moving upwards in the expression tree. I would not need to search the whole expression tree. Also languages may do different things with definitions inside conditional expressions. (if (cond)
(let ((a 5))
(preamble)
(do-stuff a))
(do-stuff 6))
You can work in side effects into the expressions of a let or let*: (let* ((x (progn (widget-lock w)
(widget-x w)))
(y ...))
(...)
(widget-unlock w))
If lock-widget returns the widget, it can look like this: (let* ((w (widget-lock w))
(x (widget-x x))
(y ...))
...
(widget-unlock w))> top level lexical scope
What does this mean? Every single other language lets you write:
var x = y;
Why is Lisp seemingly the only (imperative) language that doesn't? (progn <form>)
is equivalent to <form>https://www.nongnu.org/txr/txr-manpage.html#S-F2CAF1CB
For instance
(flow 1
(+ 2)
(let x) ;; x is 3 now
(+ 3)
(let y) ;; y is 6
(+ 4) ;; pipeline value is 10 now
(+ x y)) ;; 19 returned: (+ 3 6 10)
There is a (let (var1 init1) (var2 init2) ...) syntax supported also. Both these variants act as pass-through pipe elements: they bind variables that are in scope of the rest of the pipe.However, if you use the normal (let ((var init) ...) ...) syntax, then that is not special any more; it is just the regular let being threaded, like any other operator. It does not bind variables visible to the rest of the pipe.
Outside of this, there are only let and let* which resemble the Common Lisp ones.
Note that this construct makes sense even in pure code; it was not introduced for the sake of inserting side effects between variable definitions, but for capturing the output at specific points of the pipe, binding it to a name.
Putting something like VAR everywhere now causes issues because it's not a form that returns a value. Also, there's no place to hang declare forms on it. And if it's executed conditionally, what does that mean? The var is declared on one branch but not the other?