3 ms·
> isn't making a DSL just about the most lispy thing you could do? Yes, but with great power comes great responsibility. The problem with FORMAT and LOOP is n
by lisper 4y ago
> isn't making a DSL just about the most lispy thing you could do?
Yes, but with great power comes great responsibility. The problem with FORMAT and LOOP is not that they are DSL's, but rather that they are badly designed DSLs. The reasons are different in both cases.
FORMAT is badly designed because it is write-only, kind of like Perl regexps. It is very, very hard to debug a complex FORMAT string.
LOOP, by way of very stark contrast, is very readable, even more so than regular Lisp. But it is badly designed because it is chock-full of non-orthogonal constructs. For example, WITH is completely equivalent to a set of LET bindings outside the loop, so it doesn't actually add any functionality. All it does is give you a new non-lispy syntax for creating external bindings. The semantics of FOR depend on what comes after. "for x =" does something completely different from (for example) "for x in". And the list of problems with LOOP goes on and on and on.
So the problem is not that DSLs are bad, the problem is that these DSLs are bad. But since they are part of the standard, we are stuck with them.
- lispm 4y agoThe 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. The LOOP thus gives scope for all lexical variables inside of it. 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. (let (a) ; a is initialized to NIL (declare (type integer a)) .... (loop repeat 10 do (incf a)) ... a) vs. (loop with a fixnum ; a is initialized to 0 repeat 10 do (incf a) finally (return a)) It provides a compact notation with ONE scoping construct, the LOOP macro. Similar for the LOOP name. In the same way you could argue that this is just a named BLOCK around the LOOP construct. But, again, the name inside the LOOP makes it clear that the this is the name of this LOOP construct. Btw., the ITERATE macro works in a similar way.
- 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.
- noobermin 4y agoIt's still lisp right? You can just do your "(out ...)" like the 2003 guy suggests and not use the format form if you don't like, why get rid of it for everyone else? And sharing lisp code, is that a thing people do? (this half an insult half a joke) So you can easily enforce your own ideals in your own project.
- lisper 4y ago> why get rid of it for everyone else? I'm not advocating getting rid of format, or even loop (which is by far the greater of the two evils). I'm pointing them out as cautionary tales for future DSL designers.