7 ms·
In Lisp you'd do something like (let* ((my-list '(1 2 3)) (my-other-list '(4 5 6)) (final-list (concatenate 'list my-list my-other-li
by neutronicus 3y ago
In Lisp you'd do something like
(let* ((my-list '(1 2 3))
(my-other-list '(4 5 6))
(final-list (concatenate 'list my-list my-other-list)))
(whatever-cl-calls-join final-list ""))
This is a single Lisp form evaluated at the REPL that you could get back and edit as necessary. It's been a decade or so haha but that's the idea
- ogogmad 3y agoBroad question about Lisp conventions: Why can't you just define each variable in a separate line, instead of batching those definitions up at the top? This seems like a confusing break from convention. In C-derived languages, you can stick variable definitions anywhere. I've looked it up and Common Lisp doesn't seem to provide such an option. Seems like an odd restriction. Oh, and the difference between LET, LET* and LETREC is another weird break from convention.
- dwringer 3y agoYou 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.
- kragen 3y agoi'm not sure it's correct to say that lisp's 01959 approach to defining variables is a 'confusing break from conventions' established by c, which originated in 01972 but didn't support 'sticking variable definitions anywhere' until 01999 with the 01999 revision of the iso c standard. i would rather say that sticking variable declarations anywhere, a feature i believe was introduced to the mainstream by c++ around 01990, was the break from conventions. almost all the languages i'm familiar with from before that required you to batch up local variable declarations at the tops of scopes: c, smalltalk, pascal, scheme the exception is basic, where you could defint i or dim x(128) at any point, but it would be an understatement to say that basic's conventions were not widely emulated by other languages
- kragen 3y agoalso basic variables are global. most of these languages let you create global variables anywhere, or at least anywhere outside of a subroutine
- gknoy 3y agoI hope this doesn't derail the conversation much, but I found it interesting that you seem to be using 5 digit years, a convention I've never seen before. What drives this choice?
- kragen 3y agoit turns out it's sort of like wearing a mohawk. a long now mohawk. it's a completely harmless deviation from convention which provokes an amazing variety of reactions, some astonishingly aggressive, while other people react thoughtfully
- michael1999 3y agoThank you. I hadn't drawn the parallel before. But that matches my experience with both.
- aap_ 3y agohttps://longnow.org/ https://longnow.org/
- avgcorrection 3y ago> In C-derived languages, you can stick variable definitions anywhere. ANSI C89 requires that variable declarations only occur at the beginning of the scope.
- kazinator 3y agoMixed declarations and statements appeared in C99. C90 didn't have them. Other Algol-like languages including Algol itself also separate declarations and statements: Pascal, Modula, Ada, ... In C90, you can have variables anywhere, but they must be wrapped in a compound statement. This has a number of advantages: 1. You can repeat variables: { int yes = 1; setsockopt(fd, SOL_SOCKET, SO_WHATEVER, &yes); } { int yes = 1; setsockopt(fd, SOL_SOCKET, SO_OTHER, &yes); } 2. You can move code around more easily, and move these sub-blocks into their own functions more easily, since they follow "declaration close to use". 3. You control the end of the scope of the variables, not only the beginning. You know that after each of the above two closing braces, the yes variable is no longer in scope. (If there is a yes variable in scope, it must be coming from a parent scope; it is not "leaking" sideways.) 4. If you write a goto which goes around these encapsulated scopes, that goto does not jump into a region where the variable is uninitialized. Compare: void fn() { goto end; { int x = 42; } end: ; // x is not in scope here, OK } void fn() { goto end; int x = 42; end: ; // x is in scope here, but not initialized! } For these reasons, I avoid mixing declarations and statements in C programming; the separation is a very good idea. It's enforced in Ada, where it is rationalized with safety arguments. Mixing declarations and statements encourages bugs. It lets you hack dubious solutions into the program without having to think about how its structure could be improved to do it cleanly. In Common Lisp and similar dialects, you can mix imperative code with variable definitions by using progn. (let ((x (get-x-object) (y (progn (perform-side-effect) (get-y-object))) (z (get-z-object))) ...) On a small number of occasions, I've done the above in C, using the comma operator, to avoid mixed declarations and statements: int x = get_x(); int y = (perform_side_effect(), get_y()); It's a little dirty but not as dirty as mixed declarations and statements. Also note that there is no need for mixed declarations and statements, if your code is functional! In any of your code that emphasizes functional programming, you should not run into a need for this, because the entire purpose of the feature is to be able to stick a side effect between variable definitions, which has to be sequenced there. The Scheme language has the feature of defining variables anywhere. You can use the define form in any scope, like inside a function: (define (fun arg) (define x 0) (define y 1) ...) How this works is that Scheme implementations perform a code walk which transforms these defines into nested let forms. Each sequential body of expressions has to be scanned for the presence of define and treated this way. It's pretty ugly; you end up with what looks like a self-contained form (define x 0) that is frobbing the surrounding lexical scope, such that another form not enclosed in it depends on its definition of x. In Common Lisp and related dialects, anything that starts with "(def" is understood to be for top-level definitions only, which work by performing a run-time side effect when they are evaluated. (And so their effect is visible to later forms not due to lexical scope but due to the chronological order of execution.) There is a strong metaprogramming advantage in having rigid variable binding forms like let. Because let has all the variables in one place, it is easy to interpret and compile. This is one of the things that helps a Lisp-in-Lisp metacircular evaluator be very short. If you have variables defined anywhere, it means that every form that contains statement-like forms must be scanned for those definitions. They are not in a fixed place in the AST: (let vars form ...). That is one big reason why if you implement Scheme's define, you want that to be expanded early, so that everything downstream just sees nice nested lets.
- db48x 3y agoLisp existed before any of the conventions of languages like C were invented.
- mtlmtlmtlmtl 3y agoTypically the convention is to introduce a new scope with let. You can, if you really want to, declare the names at the top and then assign values later, similar to old C, though this is not idiomatic: (let (foo bar baz) (setf foo 2) (other-code) (setf bar (expt foo 2)) (more-code) (setf baz 5))
- g129774 3y agothe correct answer as far as LET vs LET* will be lost to all the noise, but here it goes. people are just defending a historic artifact like it actually makes any kind of sense, or like it was a deliberate design choice. it's not and it wasn't, and you're totally correct as far as it being confusing. common lisp is compromise between a variety of lisp vendors, who all were working on variously mutated dialects of the same language in the 80s, while the languages was evolving from the 60s! much of the languages functionality was added while people were figuring out how to do high level programming, so constructs that seem fundamental to us weren't introduced until much later in the process, because nobody knew that such a thing could exist. if you read documents from that era, the code that they write is wildly alien, because present day programming wasn't invented yet. things that you take for granted just didn't exist. common lisp and to a lesser extent scheme carry all that baggage for backwards compatibility. so when lisp came out, it didn't have LET, and LET didn't appear until mid 70s. and it was added as a convenience macro for ((lambda (VAR1 VAR2 ...) ...) FORM1 FORM2 ...), which is how people did dynamic binding (also called lambda-binding) for a decade prior. evolution of lisp claims that LET came from lisp machine lisp, but people were independently inventing it all over the place, as a custom macro. depending on how your lambdas were evaluated or how your LET macro was written, you're not just assigning VARs simultaneously, you might not even have a guarantee of the order of evaluation of FORMs. but it got its job done, which is according to revised scheme manual "allowing the forms for the quantities to appear textually adjacent to their corresponding variables". this was a novel convenience at some point! LET* was invented after LET as a convenience for LET, because sequential binding was even more convenient than binding in general. it would've made sense to then make LET* the "default" of some sort, but the subtle distinction stuck for reasons of legacy code, writing conventions, acquired preferences, and then it got crystalized and preserved for posterity in the common lisp standard.
- kazinator 3y ago> you might not even have a guarantee of the order of evaluation of FORMs. However, a let based on lambda would have parallel binding. The evaluation of the forms would mainly come from the argument evaluation order of the lambda, where the macro would have to go out of its way to screw it up. It's worth noting that Common Lisp has optional parameters. These use sequential binding like let*. So we could translate (let* ((a (a)) (b (b))) c) into (funcall (lambda (&optional (a (a)) (b (b))))) The lambda is called with no arguments, so that the defaulting takes place, and that has all the semantics we need. (We could also similarly exploit &aux). The fact that CL's optional parameters use sequential binding kind of shows that it's the preferred mode. The reason that the fixed parameters of lambda have parallel binding is that the values don't come from the lambda form itself, but from the arguments, which are already evaluated. So there is no way for a parameter value to be calculated from another parameter value. They come into existence at the same time. Not so with optionals; they have default expressions, and those can refer to the prior variables. Thus let came from lambda, and was understood in terms of fixed, required parameters. Required parameters come into the scope simultaneously, and so let variables came into scope simultaneously.
- kazinator 3y agoBy the way, the original author of CLISP (a free Common Lisp implementation) liked mixed variables and statements so much that the C sources use a .d rather than .c suffix and are preprocessed to .c files through a text processing script called "varbrace" which turns mixed declarations and statements into C90. That was long before C99; the project started around the middle 1980s, I think. CLISP might benefit from being updated to C99, with all the files renamed to .c, and varbrace eliminated, but I don't think anyone's gotten around to it.
- tmtvl 3y agoEh, it'd be more like... (let ((my-list '(1 2 3)) (my-other-list '(4 5 6))) (map 'String #'write-to-string (append my-list my-other-lisp))) No need for 'join the list to an empty string to convert it' shenanigans.
- kagevf 3y agoThis works: (let ((my-list '(1 2 3)) (my-other-list '(4 5 6))) (format nil "~{~A~}" (append my-list my-other-list)))