2 ms·
> The idea is that the WITH variables are local to the LOOP construct. If one leaves the LOOP, the variables are gone. With an outside LET this would not be the
by lisper 4y ago
> The idea is that the WITH variables are local to the LOOP construct. If one leaves the LOOP, the variables are gone. With an outside LET this would not be the case.
Of course it would. This:
(loop with a = ...)
is exactly equivalent to:
(let ((a ...)) (loop ...))
and so you can e.g. capture with-bindings in closures:
(funcall (loop with a = 123 return (lambda () a))) ==> 123
> Additionally the LOOP sees the WITH definition in standard Common Lisp. It does not see any outside LET bindings, unless the Common Lisp would provide a feature to ask the current environment. This allows the LOOP to for example see a type declaration and provide a default value for the common cases.
I think it is highly questionable whether providing implicit typed default values is actually a useful feature, but assuming for the sake of argument that it is, why not just make a general binding construct that does this? If it's useful, it should be useful (and usable) everywhere, not just inside a loop.
> you could argue that this is just a named BLOCK around the LOOP construct
Indeed you could. :-)
> the name inside the LOOP makes it clear that the this is the name of this LOOP construct.
Yes, but why is that useful? How is (loop named foo ...) any better than (block foo (loop ...)) ? And if it is better, then why not (progn named foo ...) ? (tagbody named foo ...) ?
- lispm 4y agoAs I said LOOP is designed such that names, local variables, local iteration variables are all defined INSIDE the LOOP, such that LOOP can process them and that they are all using the LOOP syntax. (loop with a = ...) maybe equivalent to (let ((a ...)) (loop ...)) but (let ((a ...)) ... (loop ...) ...) is not equivalent to (loop with a = ...) The difference is that in the latter it is not clear to the Lisp developer that A is meant as a LOOP local variable. That's a design choice, not bad design. Historically LOOP was named FOR and was only a tiny part of CLISP (Conversational Lisp) in Interlisp. There CLISP had a different syntax than normal Lisp, including iteration, binding, infix operations, etc. The idea of such a LOOP macro was then brought to Maclisp, ZetaLisp and, eventually, ANSI Common Lisp.
- lisper 4y ago> The difference is that in the latter it is not clear to the Lisp developer that A is meant as a LOOP local variable. That's because in the latter example, A is NOT a loop-local variable. If you write: (let ((a ...)) (some-form)) then it is clear that a is local to (some-form) regardless of what (some-form) actually is. If you write: (let ((a ...)) (some-form) (some-other-form)) then obviously it is not clear whether a is local to (some-form) or (some-other-form) or both. But so what? If it matters that this be clear, just don't put multiple forms inside the body of your LET, and then it is clear. You don't need to design a whole new language to solve this non-existent problem. > That's a design choice, not bad design. It introduces a lot of additional complexity, including a syntax that is radically different from anything else in the language, and confers very little benefit. If that is not diagnostic of a bad design I can't imagine what would be.
- lispm 4y ago> You don't need to design a whole new language to solve this non-existent problem. The designers thought that a whole new language would be useful. One may disagree with it. I would have preferred something like the ITERATE macro, but it has exactly the same idea of ENCLOSING all of these LOOP clauses, but with more parentheses. (iterate (with a = 1) (incf a) (when (> a 10) (return a))) is (loop with a = 1 do (incf a) when (> a 10) return a) If one cares about radically different syntax then you wouldn't use LOOP at all. (loop for i in l do (print i)) is equivalent to (dolist (i l) (print i)) which is equivalent to something with a LET/TAGBODY... construct. Why not just use the latter? Maybe its design choices made it practical enough?
- lisper 4y ago> The designers thought that a whole new language would be useful. Yes, obviously they thought this. They were wrong. > ITERATE Iterate is a slight improvement over loop because at least it doesn't require a whole new editor mode to format it properly, but simply adding parens to loop doesn't really fix it. The lack of parens is the least of LOOP's problems. I'll give you two more examples of problems with loop. This is still nowhere near an exhaustive list. 1. There is no way to iterate over a sequence. You can iterate over a list, or you can iterate over a vector, but you cannot efficiently iterate over something that is either a list or a vector. You have to write something like: (typecase seq (cons (loop for item in seq ...)) (vector (loop for item across seq ...))) which forces you to duplicate all of the LOOP code twice. Worse, you have to actually duplicate all the code represented by the elipses. You can't abstract it away in a function or a macro. 2. LOOP is not extensible. There are a lot of things I might want to be able to loop over, like streams, but I can't. There are a lot of control constructs I might want to embed in a loop, like with-open-file, but I can't. Instead, I have to resort to manually writing idioms like: (loop with stream = (open path) with eof-marker = (gensym "EOF") for thing = (read stream nil eof-marker) while (not (eq thing eof-marker)) ... finally (close stream)) and again, because of the non-orthogonal syntax, I can't abstract all that away in to a macro either. I have to write it all out manually every single time I want to iterate over the contents of a file. (And after all that it doesn't even do the right thing if the loop code signals a condition!)
- nerdponx 4y agoCouldn't you make a similar argument about DO? I don't see a lot of value in "X is just Y and Z, why use X?" arguments. Clearly it has enough ergonomic benefit that people like using it. I definitely think the design of the DSL is a little chaotic, not to mention difficult to remember, and having two entirely different DSLs implemented by the same macro is downright ridiculous. But it's definitely good enough at its job and better than not having it at all. If it didn't exist, people would probably be DIYing their own (even worse) versions of it, without the assurance of it being part of the specification and ostensibly a well-tested part of any implementation.
- deleted 4y ago[deleted]