News for hackers. Stuff that matters.
Re:Reading a Programmer's Guide to Common Lisp by pfdietz on Thursday February 22, 2024 @09:41AM (#39467760)
Common Lisp doesn't have a top level lexical scope, which seems to be what you're looking for here.

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?

Re:Reading a Programmer's Guide to Common Lisp by HexDecOctBin on Thursday February 22, 2024 @09:58AM (#39468033)
Why is this a problem for Common Lisp, and not for the dozens of Scheme implementations that allow you to (define ...) anywhere? (never mind what the RnRS standard says)

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

Re:Reading a Programmer's Guide to Common Lisp by dreamcompiler on Thursday February 22, 2024 @12:18PM (#39470119)
> Instead, CL forces you to do this:

  (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))
Re:Reading a Programmer's Guide to Common Lisp by pfdietz on Thursday February 22, 2024 @11:10AM (#39469189)
Nesting is a general problem beyond let, so it deserves a general solution. A solution in Lisp is the nest macro.

https://fare.livejournal.com/189741.html

Re:Reading a Programmer's Guide to Common Lisp by medo-bear on Thursday February 22, 2024 @10:16AM (#39468307)
> Do you see the problem?

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.

Re:Reading a Programmer's Guide to Common Lisp by HexDecOctBin on Thursday February 22, 2024 @10:32AM (#39468574)
So, every other language that doesn't create a new nested scope for every consecutive group of intermediate variables is doing it wrong? Which is to say, almost every language apart from Common Lisp. Does that sound reasonable to you?
Re:Reading a Programmer's Guide to Common Lisp by kragen on Thursday February 22, 2024 @10:53AM (#39468890)
while i'm not sure lisp's approach is better in this case, i do think it's fairly common that 'almost every language apart from ... lisp' 'is doing it wrong', so i don't think that would be an unreasonable position to hold on those grounds ;)
Re:Reading a Programmer's Guide to Common Lisp by a-french-anon on Thursday February 22, 2024 @11:00AM (#39469016)
You're talking about a special operator, a language primitive. In that view, let is perfectly fine and clear. Macros are what you want if syntactic sugar is needed.
Re:Reading a Programmer's Guide to Common Lisp by medo-bear on Thursday February 22, 2024 @12:47PM (#39470547)
I'd instead point out that an appeal to majority is a basic fallacy.

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.

Re:Reading a Programmer's Guide to Common Lisp by Karrot_Kream on Thursday February 22, 2024 @02:09PM (#39471588)
You've rediscovered another essential portion of Lisp: the smug lisp weenie.
Re:Reading a Programmer's Guide to Common Lisp by kazinator on Thursday February 22, 2024 @05:06PM (#39473977)
> So, every other language that doesn't create a new nested scope for every consecutive group of intermediate variables is doing it wrong?

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

Re:Reading a Programmer's Guide to Common Lisp by shawn_w on Thursday February 22, 2024 @01:33PM (#39471143)
Scheme only allows internal defines at the start of a block, where they're all combined into a letrec (or at least treated the same way). A few do allow define anywhere; SRFI-245, which gives a formal description of the semantics, lists 5 implementations that do, including some popular ones, but it's hardly dozens.

Racket also allows it and encourages it in the style guide, using the "less nesting" (and thus shallower indentation ) rationale you bring up.

https://srfi.schemers.org/srfi-245/srfi-245.html

Re:Reading a Programmer's Guide to Common Lisp by lispm on Thursday February 22, 2024 @04:35PM (#39473595)
> And some Scheme code:

    (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.
Re:Reading a Programmer's Guide to Common Lisp by kazinator on Thursday February 22, 2024 @04:46PM (#39473743)
Since (preamble) cannot possibly perform a side effect which affects the initializing expression 5, it can be moved:

  (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))
Re:Reading a Programmer's Guide to Common Lisp by ogogmad on Thursday February 22, 2024 @09:49AM (#39467892)
Why would a variable declaration be executed conditionally, unless you can somehow GOTO past the declaration? CL doesn't let you do that, does it?

> 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?
Re:Reading a Programmer's Guide to Common Lisp by pfdietz on Thursday February 22, 2024 @11:15AM (#39469246)
One reason to not allow this is that it breaks the equivalence that (for example)

   (progn <form>)
is equivalent to

    <form>
Re:Reading a Programmer's Guide to Common Lisp by martinflack on Thursday February 22, 2024 @12:39PM (#39470415)
^ This is huge. A lot of top-level macro usage would be really annoying to implement if wrapping a PROGN around top-level commands neutered their effect.
Re:Reading a Programmer's Guide to Common Lisp by medo-bear on Thursday February 22, 2024 @10:25AM (#39468444)
> Why would a variable declaration be executed conditionally, unless you can somehow GOTO past the declaration? CL doesn't let you do that, does it?

http://clhs.lisp.se/Body/s_tagbod.htm#tagbody

http://clhs.lisp.se/Body/s_go.htm#go

Re:Reading a Programmer's Guide to Common Lisp by kazinator on Thursday February 22, 2024 @05:44PM (#39474375)
TXR Lisp has a form of unhinged let in the "opip syntax" that underlies all of its threading macros.

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.

Re:Reading a Programmer's Guide to Common Lisp by pfdietz on Friday February 23, 2024 @03:32AM (#39478274)
There's a de facto standard macro for Common Lisp that does something similar, called nest. It's in UIOP.