5 ms·
You can do each variable on its own line with stuff like "defvar" and "setf", but that will add them to the current package namespace - and convention is to avo
by dwringer 3y ago
You can do each variable on its own line with stuff like "defvar" and "setf", but that will add them to the current package namespace - and convention is to avoid polluting namespaces with names that will be unused later. So, if the variables are only used within a given scope, the scope is explicitly delineated with a "let" block. It's not a general requirement.
I'm not sure what you mean about the difference between LET and LET* (the latter simply lets subsequent variable declarations refer to previously declared variables in the same block), and LETREC is not a builtin part of Common Lisp.
- ogogmad 3y agoI think SETF only reassigns a variable that's already been declared. DEFVAR defines a dynamically-scoped global variable, no? So why doesn't Common Lisp let you write `(VAR new-var new-val)` like every single other language, and have it declare a variable in the current scope? Unless there's a good reason, this is yet another obstacle to these languages being adopted by anybody except the die-hards. If you want to carefully delimit scope, doesn't Common Lisp (and other Lisps) provide PROGN? Seems like a less complicated approach. > I'm not sure what you mean about the difference between LET and LET* (the latter simply lets subsequent variable declarations refer to previously declared variables in the same block Why does LET even exist as an alternative to LET*? Why does Lisp even bother making this distinction?
- pfdietz 3y agoCommon 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?
- ogogmad 3y agoWhy 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?
- medo-bear 3y ago> 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_tagbod.htm#tagbody http://clhs.lisp.se/Body/s_go.htm#go http://clhs.lisp.se/Body/s_go.htm#go
- pfdietz 3y agoOne reason to not allow this is that it breaks the equivalence that (for example) (progn <form>) is equivalent to <form>
- martinflack 3y ago^ 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.
- HexDecOctBin 3y agoWhy 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.
- deleted 3y ago[deleted]
- medo-bear 3y ago> 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.
- HexDecOctBin 3y agoSo, 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?
- kragen 3y agowhile 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 ;)
- kazinator 3y agoTXR 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 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.
- pfdietz 3y agoThere's a de facto standard macro for Common Lisp that does something similar, called nest. It's in UIOP.
- lispm 3y agoProgramming languages may let you define variables by assigning them. But the scope can differ across languages. Common Lisp requires one to clearly define the scope. PROGN does not create a scope. > Why does LET even exist as an alternative to LET*? Why does Lisp even bother making this distinction? Because there is a scope difference. One can always do (let (a b c) (setf a 10) ... (setf b 20) ... (setf c (+ a b) ...) Think of (let* ((a 10) (b 20) (c (+ a b))) ...) as a short form for ((lambda (a b) ((lambda (c) ...) (+ a b))) 10 20)
- ogogmad 3y agoI know what the difference is between LET and LET*. What you haven't provided is a motivation for forcing the programmer to worry about that. It seems there's no concrete example of where unstarred LET would be better. If a programmer ever falls in the habit of sometimes using unstarred LET, then it's likely he'll make a mistake by using it where starred LET* was the right thing. Even Scheme gives this distinction an odd prominence that's not found outside the Lisp family. It seems reasonable that when I write code, I shouldn't have to stop and worry about which of the gazillion different LET forms is appropriate, especially in a high-level language which is supposed to help me write code (or read code) without worrying about irrelevant details like that.
- nanna 3y agoI think that the distinction makes the code clearer to read. LET tells a future reader that they don't need to bother scanning each variable binding for parent variables, whereas LET* tells them that they do. Seems like the same logic behind having a WHEN and UNLESS as opposed to just an IF, the former meaning that one needn't search for an 'else' block. The LISP family encourage good style.
- kragen 3y agoi'm not really sure myself, but i can think of some minor advantages with `let` you can say (let ((x y) (y x)) ... without worrying about the ordering of the variable bindings. this is more interesting for dynamically-scoped variables; in emacs lisp, for example, you might want to say (let ((case-fold-search t) (outer-case-fold-search case-fold-search)) ... (let ((case-fold-search outer-case-fold-search)) (f)) ...) so that when you call `f` it doesn't see your case-fold-search binding with `let*` you implicitly have an execution sequence over the binding forms, but in most cases that's something you're specifying by accident. `let` strongly suggests to the reader that she can consider any one of the binding forms in the list in isolation; she doesn't have to read through the first n-1 bindings to understand the nth one it's true that, in most cases, all three of them do the same thing, and this is not the most parsimonious approach
- martinflack 3y agoYou might like Guile, a form of Scheme. It lets you have define calls inside a begin call (progn equivalent) exactly as you ask.