Okay, is "loop" useful? Paul Graham mentions that the ANSI specification is pretty vague on the specifics of "loop" and as such, its use can't be recommended, etc.
Ted Sandler <tedsand...@rcn.com> writes: > Okay, is "loop" useful? Paul Graham mentions that the ANSI > specification is pretty vague on the specifics of "loop" and as such, > its use can't be recommended, etc.
It's controverisal, but my opinion is "yes, it's useful".
I used to not like it but was gradually won over, though I think good style means avoiding using its total power and sticking to things that are straightforward to read.
The reason some people don't like it is simple syntax--it's not grouped. Something like (loop for i from 0 to 100 for j from 3 for z in z-list do (something)) ought, some say, be grouped like (loop (for i (from-to 0 100)) (for j (from 3)) (for z (in z-list)) (do (something))) or at least (loop :for ((i :from 0 :to 100) (j :from 3) (z :in z-list)) :do (something)) so that Emacs can more easily indent it or so that programs didn't need a special parser to understand it. Nevertheless, loop advocates defend the syntax as simple and important to be uncluttered by the extra parens.
Ignoring the syntax, though, and just accepting that it is what it is (and what it has been for several decades), the fact is that it provides important capabilities of concisely representing the generation and collection of quantities in a very useful way. Not the only way that has ever been come up with, but still *a* useful way.
For a long time, I preferred to write
(do ((l list (cdr l)) (result '() (cons (f (car l)) result))) ((null l) (nreverse result)))
But though this has the usefulness of paralleling Scheme's tail recursive
in the end, I couldn't justify to new users why they shouldn't just write the more obvious:
(loop for item in list collect (f item))
And sure, you could also write
(mapcar #'f list)
but the point is that if you want to change the accumulation from collect to max or min, you can't make a trivial edit to fix that, whereas with the loop one you can do
(loop for item in list maximize (f item))
Likewise, things like dolist only make it easy to step the easily-steppable data structures. If you wanted to step a package in synch with a counter, you'd have to mix binders. e.g.,
Whereas with loop you can do this in a consolidated way:
(loop for symbol being the external-symbols of "COMMON-LISP" count t) => 978
Basicallly, a lot of standard idioms are there and it's really quite definitely worth being able to use it.
- - -
The other things that are controversial are the fact that some aspects of LOOP seem unnecessary at first blush. It has conditionals, for example. One is sometimes led to write
(loop for x in y when (f x) do (g x))
when one could as well write
(dolist (x y) (when (f x) (g x)))
Looking at such an example, one might ask why WHEN is there at all. But the answer turns out to be that it's *very* hard to write a macro in CL that has a "detached" piece of syntax. So you need to have a way to get to the collect point, and it's often in a conditional. Consider:
(loop for x in y when (f x) collect (g x))
This one you can't write as
(dolist (x y) (when (f x) (collect (g x))))
Nor is there another macro (even LOOP) that you could substitute for dolist that would make the "collect" part work. You *could* make a special "collecting" macro, for example, that did
but already you're in a problem where you've detached the collection from the generation, and if you have several of these to do they get harder and harder.
The ability in loop to do even complex things like:
(loop for x in '(1 2 3 4 5 6 7) when (evenp x) collect x into evens else collect x into odds finally (return (values evens odds))) => (2 4 6), (1 3 5 7)
is considerably easier and clearer than some other alternatives.
Also, being able to vary the collection is handy. Consider:
(loop for x in '(a b (c d) e f) when (atom x) collect x else append x) => (A B C D E F)
Writing the equivalent in a dolist is trickier. Consider:
(let ((result '())) (dolist (x '(a b (c d) e f)) (if (atom x) (push x result) (dolist (subx x) (push subx result)))) (nreverse result)) => (A B C D E F)
Now, personally, I have no trouble looking at either of these and seeing the same thing, but I still have to admit that the LOOP form of it is more instantly accessible not only to me but to newcomers.
Some don't like it because it seems very ALGOL-esque and they think something Lispy would be better, but those of us who've "given in to the dark side" basically acknowledge that sometimes other languages have something to offer in terms of expression and it's better not to fight things that don't seriously need fighting. People who WANT to not use this are still free to, but those who like LOOP are not idiots or crazy or possessed by the devil. They're just making an engineering trade-off.
Some also don't like that there are occasionally obscure things in LOOP you have to be careful of. It used to be in older dialects that (loop collect x for i from 1 to 3) would unintuitively return (1 2 3 4) because the collection came before the iteration test, but (loop for i from 1 to 3 collect x) would return (1 2 3) as you might expect. In ANSI CL, we've added constraints to LOOP that say the variable stepping clauses have to precede the collection parts, so this can't happen. Nevertheless, there may be a few ways you can still confuse yourself with LOOP. For example, you might think (loop with i = (random 100) for x from 1 to 10 do (print (list i x))) and (loop for i = (random 100) for x from 1 to 10 do (print (list i x))) meant the same in English, but you *must* know that FOR means to do the action every time around the loop, while WITH means to do it only once before starting. This is a major difference but people who make the mistake of thinking loop is "just English" are going to get confused by "apparent synonyms" (a.k.a. "false cognates").
Good LOOP style means knowing what the words really mean, and that means that although it seems like the best entry point for the novice, novices need to be more cautious than anyone about its use. That doesn't mean "don't use it", just "use it with care". Lisp may be great for building AI things but it does not use AI as part of its own semantics...
| but the point is that if you want to change the accumulation from collect | to max or min, you can't make a trivial edit to fix that, whereas with the | loop one you can do | | (loop for item in list | maximize (f item))
As long as we're on that topic: how can you have a (simple) loop that maximizes return a compound structure instead of the maximized value?
> Okay, is "loop" useful? Paul Graham mentions that the ANSI > specification is pretty vague on the specifics of "loop" and as such, > its use can't be recommended, etc.
Unfortunately, Paul Graham is pretty vague on the _reasons_ he does not like "loop", and as such, his recommendations cannot be trusted, etc. Ignore his hostility towards it. "loop" is simply _way_ useful. If you are blinded by an irrational desire for syntactic purity that is thwarted by the slightly unusual "loop" form (which demonstrates excellently how you can build mini-languages in macros in Common Lisp), maybe you are better off looking at Scheme? Paul Graham has some pretty bizarre Scheme envy, too, which is another reason not to trust his recommendations. Scheme people hate iteration, except when they run around in perfect circles, cursing and re-cursing Common Lisp's iteration constructs.
> * Ted Sandler <tedsand...@rcn.com> > > Okay, is "loop" useful? Paul Graham mentions that the ANSI > > specification is pretty vague on the specifics of "loop" and as such, > > its use can't be recommended, etc.
> Unfortunately, Paul Graham is pretty vague on the _reasons_ he does not > like "loop", and as such, his recommendations cannot be trusted, etc. > Ignore his hostility towards it. "loop" is simply _way_ useful. If you > are blinded by an irrational desire for syntactic purity that is thwarted > by the slightly unusual "loop" form (which demonstrates excellently how > you can build mini-languages in macros in Common Lisp), maybe you are > better off looking at Scheme? Paul Graham has some pretty bizarre Scheme > envy, too, which is another reason not to trust his recommendations. > Scheme people hate iteration, except when they run around in perfect > circles, cursing and re-cursing Common Lisp's iteration constructs.
Seems surprising, but Paul Graham thinks Common Lisp sucks:
Some quotes from "Being Popular" by Paul Graham, March 2001 :
"The political correctness of Common Lisp is an aberration."
"A more serious problem is the diffuseness of prefix notation. For expert hackers, that really is a problem. No one wants to write (aref a x y) when they could write a[x,y]."
"The good news is, it's not Lisp that sucks, but Common Lisp."
"Using first and rest means 50% more typing. And they are also different lengths, meaning that the arguments won't line up when they're called, as car and cdr often are, in successive lines. "
Boris Schaefer <bo...@uncommon-sense.net> writes: > Kent M Pitman <pit...@world.std.com> writes:
> | but the point is that if you want to change the accumulation from collect > | to max or min, you can't make a trivial edit to fix that, whereas with the > | loop one you can do > | > | (loop for item in list > | maximize (f item))
> As long as we're on that topic: how can you have a (simple) loop that > maximizes return a compound structure instead of the maximized value?
> This will return 10. I want it to return foo1. Is there some easy > way to accomplish this with maximize?
It's unfortunate that the ability to define other loop-paths was omitted from the standard, since most implementations have such a thing. Some things I think you have to just code longhand.
(loop with best-foo = nil ;untested for foo in (list foo1 foo2) when (or (not best-foo) (> (weight foo) (weight best-foo))) do (setq best-foo foo) finally (return best-foo))
All in all, I think this is still pretty readable, though I'd accept the classic
as equally good in this case. LOOP isn't, as spec'd, a panacea for everything. But its usefulness decays relatively gracefully, allowing you to fall back on weaker clauses rather than just having you fall out of it suddenly if you reach a thing you can't do in the totally-easy way.
Someone has to speak for the people who dislike the loop macro so it might as well be me.
The beauty of Lisp is that function follows form. The uninitiated often laugh at all the parentheses in a Lisp function. The Lisp expert however sees in those parens the structure of the code. It's like looking at the framework of a building being built and being able to envision the function of every part of it.
The loop macro ruins this. You have an opening paren and a closing parens and then a collection of english words with stuff in between. There is no longer any structure visible. You have to read the form as you would a novel with very obscure and convoluted sentence struture. (see http://www.bulwer-lytton.com/ for convoluted sentence structure examples).
Everyone who've I've seen attracted to the loop macro starts out swearing that they will only use it for simple loops but soon get loop fever and it's a challenge to them to write the most obscure unreadable loop macros they can, using every single loop macro keyword.
my advice: Avoid loop like the plauge. Stick with all those parens. Make it your goal that your code will not only work but will be readable and modifyable by others. Yes it may take more characters to write your code. So what. Yes you may find yourself building the same frameworks over and over again (e.g. to collect results from an iteration) but as Kent pointed out when you read this code you recognize common idioms like this simply from the structure. In fact you can see what the code is doing from the framework alone (the paren layout) without even reading what's inside the parens.
Learn Lisp first. Give it a chance before you start in on the loop language.
John Foderaro wrote: > Someone has to speak for the people who dislike the loop macro so it > might as well be > me.
> The beauty of Lisp is that function follows form. The uninitiated often > laugh at > all the parentheses in a Lisp function. The Lisp expert however sees in > those parens > the structure of the code. It's like looking at the framework of a > building being built and being able to envision the function of every part > of it.
> The loop macro ruins this. You have an opening paren and a closing > parens and then > a collection of english words with stuff in between. There is no longer > any structure > visible. You have to read the form as you would a novel with very obscure > and convoluted sentence struture. (see http://www.bulwer-lytton.com/ for > convoluted sentence structure examples).
> Everyone who've I've seen attracted to the loop macro starts out swearing > that they > will only use it for simple loops but soon get loop fever and it's a > challenge to them to write the most obscure unreadable loop macros they > can, using every single loop macro keyword.
> my advice: Avoid loop like the plauge. Stick with all those parens. > Make it your > goal that your code will not only work but will be readable and modifyable > by others. > Yes it may take more characters to write your code. So what. Yes you > may find yourself building the same frameworks over and over again (e.g. > to collect results from an iteration) but as Kent pointed out when you > read this code you recognize > common idioms like this simply from the structure. In fact you can see > what the code is doing from the framework alone (the paren layout) without > even reading what's inside the parens.
This is a reasonable statement against what may be seen bad with the LOOP macro - but then I still do not understand how you could then recommend the usage of your IF* macro which IMHO destroys too the structure of Lisp code you speak above. With IF* it is even worse because probably through the nature of it I've seen that often alot of "normal structured" Lispcode is used between the THEN ELSE a.s.o. tags (sometimes more than fits on a screen!!!). It has the same problems like LOOP with editors like EMACS but brings IMHO not very much gain instead of a questionable kind of syntactic sugar that could be better done by appropriate use of IF, UNLESS and WHEN.
In your style-guide you furthermore advice against the usage of WHEN and UNLESS with arguments that sound IMHO more plausible for Scheme then for Common Lisp (IF, UNLESS and WHEN would be ambigous concepts)
For those that really want to avoid "unlispy syntax" I would recommend then to avoid IF* and better use IF, WHEN and UNLESS appropriate and furthermore use the ITERATE macro instead of the standard extended LOOP macro.
Ted Sandler wrote: > Okay, is "loop" useful? Paul Graham mentions that the ANSI > specification is pretty vague on the specifics of "loop" and as such, > its use can't be recommended, etc.
I would say that LOOP is a immensily useful thing that can make certain things alot easier to do (as Kent already pointed out).
I think the key is to recognize when _not_ to use LOOP. It is imho nicer to write e. g.
(dolist (i '(1 2 3)) (print i))
than
(loop for i in '(1 2 3) do (print i))
or
(dotimes (x width) (dotimes (y height) (setf (aref *map* x y) (+ x y))))
instead of
(loop for x from 0 below width do (loop for y from 0 below height do (setf (aref *map* x y) (+ x y))))
If you don't need the special accumulation features (collect, nconcing, append, maximize...) or the more declarative arithmetic FOR loop:
(loop for i from m to n by o ...)
then it is probably a better idea to use the other facilities.
John Foderaro <j...@unspamx.franz.com> writes: > Someone has to speak for the people who dislike the loop macro so > it might as well be me.
Thanks for volunteering! It's hard to debate with "unnamed others".
> The beauty of Lisp is that function follows form.
"The". Hmm. Surely there are other beauties. Also, there are bound to be exceptions, and I think there are good reasons to believe that LOOP and FORMAT, with their "alternate languages", occur in places that one might reasonably expect such exceptions to crop up.
I/O is quite complicated, yet not always "important" (in the sense of wanting to be the visual focus). Yet in Maclisp before FORMAT was common, the use of (progn (terpri) (princ '|There |) (princ (cond ((= n 1) '|is|) (t '|are|))) (princ '| |) (let ((base 10)) (princ n)) (princ '| |) (princ name) (cond ((not (= n 1)) (princ '|s|))) (princ '| here.|)) would completely dominate the landscape, making it hard to find the computation. We still fall back to this kind of thing once in a while, but we gave up our general syntax in the common case to gain something very valuable: conciseness and focus.
Likewise, for iteration, the business of generating, filtering, and accumulating is very heavily idiomatic and to go and *implement* it every time instead of to call up the idiom makes it necessary for people to repeat sometimes-obscure incantations that they ought not to have to know and that sometimes might even improperly optimize for a given implementation. I agree it loses a little of the elegance you cite, but not irrationally. There is a very definite gain to be had.
> The uninitiated often laugh at > all the parentheses in a Lisp function. The Lisp expert however sees in those parens > the structure of the code.
Is this a proposal that the correct fix to LOOP is a different way to parenthesize it? If not, it seems like smokescreen. If so, I'd be curious to see your proposed parenthesization. I've found in my few tries that it's painful to do because some groupings either divide things that ought not be divided or don't divide things that ought. e.g., for x from 3 to 5 Should this be (x (from 3) (to 5)) and if so what about the from-to relationship? or (x (from-to 3 5)) in which case what about the (from 3)/(to 5) associations?
> Everyone who've I've seen attracted to the loop macro starts out > swearing that they will only use it for simple loops but soon get > loop fever and it's a challenge to them to write the most obscure > unreadable loop macros they can, using every single loop macro > keyword.
I've never seen this happen. We must have a non-overlapping set of acquaintances.
> my advice: Avoid loop like the [plague]. Stick with all those parens.
But what goes in them? I posted several LOOP examples that I don't think were candidates for any hypothetical obfuscated-lisp award. Perhaps you could go through those and post your preferred alternative.
In article <9mb3r4$t1...@rznews2.rrze.uni-erlangen.de>, j...@dataheaven.de says...
> This is a reasonable statement against what may be seen bad with the LOOP > macro - but then I still do not understand how you could then recommend the > usage of your IF* macro which IMHO destroys too the structure of Lisp code > you speak above.
Destroys? I don't see that at all. In fact if indented properly (we supply the emacs code to do this) then it actually displays the structure of a conditional far better than any alternative.
Yes if* has english language 'keywords' (then, else and elseif) but unlike loop you don't have to read the keywords to know what the code does. They just exist to separate parts of the conditional.
For example you can write (if* aaaaaa then bbbbbb else cccccc) but you can't write (if* aaaaaa else cccccc then bbbbbb). You know that after the predicate will be a 'then'. You don't even have to read it. The 'else' is there just so you know that cccccc doesn't follow bbbbbb which you don't see in (if aaaaaa bbbbbb cccccc). With loop you must read every keyword to understand what's going on.
Readability is in the mind of the reader. AllegroServe is an example of a large program written in the style I prefer (no loop macro (other than the no keyword version), no if, when and unless). Everyone can judge for themselves whether this style works. (source available at ftp://ftp.franz.com/pub/aserve/ )
Boris Schaefer <bo...@uncommon-sense.net> writes: > Kent M Pitman <pit...@world.std.com> writes:
> | but the point is that if you want to change the accumulation from collect > | to max or min, you can't make a trivial edit to fix that, whereas with the > | loop one you can do > | > | (loop for item in list > | maximize (f item))
> As long as we're on that topic: how can you have a (simple) loop that > maximizes return a compound structure instead of the maximized value?
> This will return 10. I want it to return foo1. Is there some easy > way to accomplish this with maximize?
It's not quite what you're asking, so raising this is this context is perhaps a little cheeky, but my quest for making people aware of wheels and reinvention leads me to suggest googling on groups.google.com for "lisp morally-biggest" (this is not meant to be intuitive), which will lead you to a dubious implementation of a decent function. There were followups with better implementations (O(n) rather than O(n^2), for instance).
John Foderaro wrote: > In article <9mb3r4$t1...@rznews2.rrze.uni-erlangen.de>, j...@dataheaven.de > says... >> This is a reasonable statement against what may be seen bad with the LOOP >> macro - but then I still do not understand how you could then recommend >> the usage of your IF* macro which IMHO destroys too the structure of Lisp >> code you speak above.
> Destroys? I don't see that at all. In fact if indented properly > (we supply the emacs code to do this) then it actually displays > the structure of a conditional far better than any alternative.
This is a question of personal taste I fear - but IF* is definitely similar controversial like the extended LOOP facility.
> Yes if* has english language 'keywords' (then, else and elseif) > but unlike loop you don't have to read the keywords to know > what the code does. They just exist to separate parts of > the conditional.
> For example you can write (if* aaaaaa then bbbbbb else cccccc) > but you can't write (if* aaaaaa else cccccc then bbbbbb). > You know that after the predicate will be a 'then'. You don't > even have to read it. The 'else' is there just so you know > that cccccc doesn't follow bbbbbb which you don't see in > (if aaaaaa bbbbbb cccccc). > With loop you must read every keyword to understand what's going on.
in
(if aaaaaa then bbbbb elseif ccccc then ddddd else eeeee)
I have to read the keywords if I not indent the clauses appropriate (which no bulk emacs will do right!)
But
(if aaaaa bbbbb (if ccccc ddddd eeeee))
is automaticall indented right by emacs
and even
(if aaaaa bbbbb (if ccccc ddddd eeeee))
is easier to glance at for me because the inner if is nested appropriately.
I see no real gain in doing it like IF* does - It did not happen very often to me that I've written an IF were I really had to place an PROGN to encapsulate the clauses. IMHO IF ic nicely indented by Emacs&Co. so that it is pretty readable where the then and where the else clause is.
(if condition (then-clause ..) (else-clause ..))
against
(if* condition then (then-clause ..) else (else-clause ..))
Besides of the fact that it maks the code unnecessary longer it leads to the problem that the clauses are indented on the same level. If the clauses are a bit bigger than shown here the little else is visually vanishing between.
> Readability is in the mind of the reader. AllegroServe is an example > of a large program written in the style I prefer (no loop macro (other > than the no > keyword version), no if, when and unless). Everyone can judge for > themselves whether > this style works. (source available at ftp://ftp.franz.com/pub/aserve/ )
Yes I know - and since I spent enough time with AllegroServe to port it to e. g. LispWorks I can say that IF* is most of the times more difficult to decipher for at least me and up to now all other contributors I've spoken to. For Portable AllegroServe we have chosen to let the IF* that are already in unchanged since changing it would be more hassle than ignoring it. But we've chosen too that all new written/rewritten code avoids the use of IF* and use extended LOOP too where appropriate (since e. g. the use of the simple LOOP is in most cases a bad decision since it hides the iteration to deeply in the code).
Please see this as constructive critique - I'm telling you only of my experience with e. g. IF* in general and besides that AllegroServe. I think it is a good thing to get response - if it's positive or negative.
I'm not trying to join a debate on loop. I just wanted to present the other side of the intelligent people can then weigh the arguments on both sides.
I'm not suggesting that loop can be fixed either by adding parenthesis or coming up with ways of indenting it to make it understandable. It's a lost cause.
There is a point to be made about this though:
==== from kmp:
Yet in Maclisp before FORMAT was common, the use of (progn (terpri) (princ '|There |) (princ (cond ((= n 1) '|is|) (t '|are|))) (princ '| |) (let ((base 10)) (princ n)) (princ '| |) (princ name) (cond ((not (= n 1)) (princ '|s|))) (princ '| here.|))
==== end kmp
I believe that what you are saying is the format in Common Lisp introduced a new language to save us from having to do I/O like above, and since that was a good thing then maybe loop's introduction of a new language for iteration is a good thing as well too.
Let's look at your example though. It took you very little time to write that after knowing what you wanted it to do, and I'm sure that everyone read it very quickly and figured out what it did instantly.
Now I challenge everyone to write the equivalent using Format. Time yourself to see how long it takes.
<i'll pause here while you go do it> .. .. .. ..
Time's up..
here's my solution with a test case (i'm sure there are other ways of doing it)
(format t "There ~[are~;is~:;are~] ~:*~d ~a~2:*~p here." 1 'ball)
Can you read it and tell me without trying it whether it works or not? Give it to a co-worker who hasn't seen this thread and ask him/her what it prints? How long until you get an answer?
There's no question that the format string is more concise. But is using it better? Is writing code with the least number of characters always best or is it important to write code that others can read and understand without reaching for the Lisp manual.
Another great example from kmp:
=== from kmp
For example, you might think (loop with i = (random 100) for x from 1 to 10 do (print (list i x))) and (loop for i = (random 100) for x from 1 to 10 do (print (list i x))) meant the same in English, [but they don't do the same thing in loop]
=== end kmp
loop lulls you into thinking that you understand the program since you understand English. Make no mistake about it, loop is its own language. If you use it you condem everyone who reads the code to also learn the loop language.
Just as an example to the others about what we're talking about I took a code segment out of AllegroServe wrapped in a defun for realism and I'll show you what it looks like with if* (the foo function) and if (the bar function). One of the problems I have with the if special form occurs at the of the bar function. It's pretty easy to get confused and think that the dotimes at the end is always executed. At first glance you may not see the indentation difference between the if and dotimes. I've seen code where people put in comments like ";else" before such a clause since they see the problem too. All if* does is enforce that 'else' comment (in effect).
(defun foo () (if* text-mode then ; here is where we should do ; external format processing #+(and allegro (version>= 6 0 pre-final 1)) (multiple-value-setq (buffer items tocopy) (octets-to-string mpbuffer :string buffer :start cur :end (+ cur tocopy) :string-start start :string-end (length buffer) :external-format external-format :truncate t)) #-(and allegro (version>= 6 0 pre-final 1)) (dotimes (i tocopy) (setf (aref buffer (+ start i)) (code-char (aref mpbuffer (+ cur i))))) else (dotimes (i tocopy) (setf (aref buffer (+ start i)) (aref mpbuffer (+ cur i))))))
(defun bar () (if text-mode (progn ; here is where we should do ; external format processing #+(and allegro (version>= 6 0 pre-final 1)) (multiple-value-setq (buffer items tocopy) (octets-to-string mpbuffer :string buffer :start cur :end (+ cur tocopy) :string-start start :string-end (length buffer) :external-format external-format :truncate t)) #-(and allegro (version>= 6 0 pre-final 1)) (dotimes (i tocopy) (setf (aref buffer (+ start i)) (code-char (aref mpbuffer (+ cur i))))))
(dotimes (i tocopy) (setf (aref buffer (+ start i)) (aref mpbuffer (+ cur i))))))
John Foderaro <j...@unspamx.franz.com> writes: > loop lulls you into thinking that you understand the program since > you understand English. Make no mistake about it, loop is its own > language. If you use it you condem everyone who reads the code to > also learn the loop language.
This is only a problem if you foolishly assume that loop is English. It's ALGOL-inside-Lisp, which is a great example of how Common Lisp tries to support a variety of styles. Personally, I tried to suck in my Algol-like-languages gut until I really got a feel for programming in lisp, but now I love that I can let it hang out. I would expect that people who spent less time in Algol-like languages would think in Algol-ish terms less, and thus find less of a need for loop. The same as how people who grew up in Smalltalk are going to eat up CLOS, and spend a lot more of their time writing in CLOS, than others.
My natural instinct is to throw a bunch of different styles at a problem and use what sticks. I'll occasionally go thousands of lines without a loop form, but when it sticks, I use it.
loop is an excellent tool. But like a good drug, it is easily abused!
I agree that it's syntax is rather a different breed from the rest of lisp... but one thing it shares with the language as a whole is the freedom to abuse its features. I've always thought loop gives you more rope than you'll probably ever need, certainly more than enough to hang yourself!
As strictly "newbie" advice, I would say make sure your problem can't be solved easily with mapcar, dotimes or dolist first, but if loop makes it easier why hobble yourself?
> Okay, is "loop" useful? Paul Graham mentions that the ANSI > specification is pretty vague on the specifics of "loop" and as such, > its use can't be recommended, etc.
> Everyone who've I've seen attracted to the loop macro starts out > swearing that they will only use it for simple loops but soon get > loop fever and it's a challenge to them to write the most obscure > unreadable loop macros they can, using every single loop macro > keyword.
> my advice: Avoid loop like the plauge.
Ahem! ... and then use IF* when working with ACL?
Sorry, I couldn't resist. :)
Cheers
-- Marco Antoniotti ======================================================== NYU Courant Bioinformatics Group tel. +1 - 212 - 998 3488 719 Broadway 12th Floor fax +1 - 212 - 995 4122 New York, NY 10003, USA http://bioinformatics.cat.nyu.edu "Hello New York! We'll do what we can!" Bill Murray in `Ghostbusters'.
John Foderaro <j...@unspamx.franz.com> writes: > (format t "There ~[are~;is~:;are~] ~:*~d ~a~2:*~p here." 1 'ball)
You've made it artificially hard and you seem to think this would make it hard to write. You *can* do it that way, but you don't have to. Anyone who is straining will not spend a long time looking up hard ways, they'll do it the same easy way I'd have written:
(format t "There ~:[are~;is~] ~D ~A~P here." (= n 1) n name n)
> Can you read it and tell me without trying it whether it works or not?
No, but I can trivially vary the args and execute it in an interactive environment to see if it works, and thereafter I can just glance at it and say "seems like it's doing the right thing, glad it's not taking up much space".
> There's no question that the format string is more concise. But is > using it better?
You lived through those days of Maclisp and its companion dialect "Franz Lisp" (no relation to Allegro CL for those of you who weren't there). I'm surprised you can ask this. I still have my old code listings and it's obvious at even just a glance that this was a change 100% for the better. No question. It's like the change from Assembly language to a higher level language. Formatted I/O is nitpicky and you can either let it take over your visual space or you can beat it into submission by a compaction technology like this; I vote for the latter. But we didn't quash the long-winded way in the process; we retained both styles and people always have the option. It's *plain* by inspecting the code of most people these days that we made a good choice giving people this option; moreover, for those who aren't satisfied there are other alternatives.
I'm not advocating LOOP-only nor FORMAT-only. I'm advocating that they are well-placed in the language because they appeal to a large audience, and I'm further arguing that that audience is not characterized by the oversimplistic attribute "tasteless". There are good and even tasteful reasons for using both of these. Using LOOP and FORMAT certainly do not uniquely define taste. I'm not arguing that your desire to use something else is tasteless. I'm not arguing that there weren't aesthetic trade-offs made. But I think it goes a little far to say "avoid it like the plague".
> Is writing code with the least number of characters always best
Absolutely not. But I haven't argued that. I have argued that code which reasonably focuses the reader on things the reader cares about is important. For example, LOOP's ability to name intent ("collect", "maximize", etc.) is a virtue independent of code length that is lost in other variants of code which might "implement" but not "express" that idea. In particular, the idiom of push-push-push-nreverse cannot be argued, I finally decided of my own code (which used to use this heavily), to be perspicuous as to the fact of its collection of a forward list. It's an idiom that implements the collection of a forward list but it looks weird as all get-out. LOOP fixes that by hiding whatever accumulation technique it uses. Further, LOOP *also* allows implementational flexibility to do better than I would have done, and that is also an aesthetic which goes beyond simple numbers of characters.
> or > is it important to write code that others can read and understand > without reaching for the Lisp manual.
You set up the straw man of the hard to read FORMAT string, but I don't think it has to be that way. I think people should try to keep things simple. And I often resort to combinations of Lispy iterative techniques and FORMAT rather than use some of the more baroque list and conditional accessors. However, I don't mind it when others don't; I trust them to have debugged what they did (partly) and when I don't I just construct a test case, which usually is quite easy.
> Another great example from kmp:
> === from kmp
> For example, you might think > (loop with i = (random 100) for x from 1 to 10 do (print (list i x))) > and > (loop for i = (random 100) for x from 1 to 10 do (print (list i x))) > meant the same in English, [but they don't do the same thing in loop]
> === end kmp
> loop lulls you into thinking that you understand the program since > you understand English. Make no mistake about it, loop is its own > language.
We do agree on this code being something you have to learn to read, but I think this is a manageable risk.
> If you use it you condem everyone who reads the code to > also learn the loop language.
Somewhat. Though mostly only if really trying to debug the code. Code will often have tremendous subtlety that one doesn't see at first blush. One usually just goes ahead and glances at it and trusts it until one zooms in--and I find it helpful if, when in this mode of scanning from high above, like a bird looking for a place to land, if the general landmarks are clearly marked. LOOP does that. It draws my eye to the fact that a list traversal is happening, or a count is being done, etc. Then I can debug it if I have to, but that's very useful skimming info. So there are two issues: how do I debug a LOOP (which I agree can involve a bit of care) and how do I let a probably-debugged LOOP guide me (which can help a lot, though if it's not really debugged can be confusing). Still, merely learning "caution" in over-believing LOOP is important.
Btw, one other factor we didn't mention and that's debugging. The only way to debug an out-of-control LOOP is by macroexpanding it. You can't always visually step it (though modern LOOP can do this a little easier than some of the earlier LOOP variants you and I grew up on). I agree that's an issue, but again I think it's manageable.
> Boris Schaefer <bo...@uncommon-sense.net> writes: > > As long as we're on that topic: how can you have a (simple) loop that > > maximizes return a compound structure instead of the maximized value?
> It's unfortunate that the ability to define other loop-paths was omitted > from the standard, since most implementations have such a thing.
I agree to the point that I load the MIT-LOOP code in Lispworks and shadow CL:LOOP and CL:LOOP-FINISH, but I don't see how add-loop-path can be used to solve this problem.
-- Lieven Marchand <m...@wyrd.be> She says, "Honey, you're a Bastard of great proportion." He says, "Darling, I plead guilty to that sin." Cowboy Junkies -- A few simple words
| John Foderaro <j...@unspamx.franz.com> writes: ... | Is this a proposal that the correct fix to LOOP is a different way to | parenthesize it? If not, it seems like smokescreen. If so, I'd be curious | to see your proposed parenthesization. I've found in my few tries that it's
Are you aware of Iterate <http://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/lisp/code/...>? (The distribution archive contains two Postscript documents (the manual and "Don't Loop, Iterate" paper) that make good reading for those unfamiliar with the package.) I think it comes pretty close to a nice "parenthesized LOOP". Then again, I also think it is even too close to a parenthesized LOOP in some ways.
| painful to do because some groupings either divide things that ought not be | divided or don't divide things that ought. e.g., | for x from 3 to 5 | Should this be
IMO, there are two alternatives depending on how generic FOR you want. If you want a LOOP-like FOR that can assign to the given variable numbers of some range, elements of a list, vector etc., I think your example should be:
(for x (scan-range :from 3 :to 5))
The version for the elements of a list would be:
(for x (scan '(1 2 3)))
If you know Series, you should realize that what I just depicted is FOR that assigns to its first argument elements of series given as its second argument. FWIW, I might have used shorter RANGE instead of SCAN-RANGE if I didn't want to use existing scanners from the Series package. Also note that I'm not saying this is what I think the full Series-FOR syntax should be; it is just what is needed to handle your example.
If you don't want that generic FOR, then I think your example should/could be:
(for x :from 3 :to 5)
While you _could_ make this kind of FOR at least as generic as LOOP's FOR (obviously not as generic as the Series-FOR above, though), I think it would be a bad idea. A better idea would probably be to have a separate form (actually, more than one form might be needed) for iterating over the elements of different containers.
| > my advice: Avoid loop like the [plague]. Stick with all those parens. | | But what goes in them? I posted several LOOP examples that I don't think | were candidates for any hypothetical obfuscated-lisp award. Perhaps you | could go through those and post your preferred alternative.
I haven't read the whole thread but I'm sure those examples would be handled nicely by Iterate.
> You have an opening paren and a closing parens and then a collection of > english words with stuff in between. There is no longer any structure > visible. You have to read the form as you would a novel with very > obscure and convoluted sentence struture.
Yeah, ick! That description kind of reminds me of stuff like this:
(if* (not (probe-file orig-dxl)) then (setq prev (rename-file dxl-file orig-dxl)) elseif (not (probe-file old-dxl)) then (setq prev (rename-file dxl-file old-dxl)))
> Everyone who've I've seen attracted to the loop macro starts out swearing > that they will only use it for simple loops but soon get loop fever and > it's a challenge to them to write the most obscure unreadable loop macros > they can, using every single loop macro keyword.
You have seen my code. I do not do that. I have seen lots of other people's code, and they do not do the horrible things you conjure up. Please do not insult people's intelligence so gravely by pretending that you do not know that you exaggerate wildly and irrationally and that you think nobody would be smart enough to arrest you for it. They do, and it makes your point ridiculous and dishonest. Coupled with the fact that you do use that fantastic if* monster, which suffers from even more of the problems you have described for loop, the intelligent reader will sit back and wonder what the hell you _really_ are opposed to about loop as none of your lofty principles apply to another obvious candidate.
> My advice: Avoid loop like the plauge. Stick with all those parens. > Make it your goal that your code will not only work but will be readable > and modifyable by others.
Excellent argumentation, and I mean that. Pity it is not applied to if*, too, which is such an incredibly horrible concoction that I have a really hard time dealing with those who use it, and not just their code. Unlike loop, which does some pretty useful things, if* is but a wrapper around cond to get rid of progn, making your code unreadable and unmodifiable by those who happen to think that this is good advice, slightly paraphrased:
> Learn Lisp first. Give it a chance before you start in on the if* language.
Please get rid of if* in published Franz Inc code, and I can live with your verbose iteration idioms and the moderately supported fear that loop is underoptimized for political reasons. When you have cleaned up the if* act, I will probably listen to you on a lot of other issues, too, but as long as you rant and rave about loop while you use if*, I am sorry, but I cannot take you very seriously. It looks too much like a religious issue that has none of the redeeming qualities of actual religions.
John Foderaro wrote: > One of the problems I have with the if special form occurs at the of the > bar function. It's pretty easy to get confused and think that the dotimes > at the end is always executed. At first glance you may not see the > indentation difference between the if and dotimes.
where the BAR function looks, in outline, like this.
(defun bar () (if something stuff-to-do-when-true stuff-to-do-when-false))
It seems to me that the blame here lies not with IF, but with the peculiar convention, which I have never understood, that indents the "then" and "else" parts of an IF differently. It would almost make sense if the "else" part of an IF had an implicit PROGN, but it doesn't.
With indentation more like
(defun bar () (if something stuff-to-do-when-true stuff-to-do-when-false))
it is much harder to miss the fact that the two consequent clauses are indented by the same amount.
Perhaps someone can enlighten me. Why is IF so often indented in this strange way? (Non-answer: "Because that's what emacs lisp-mode does." So, why does emacs lisp-mode do it?)
In the particular case John Foderaro posted, the stuff-to-do-when-true is moderately long (17 lines). If that's too long to be able to see how the indentation lines up, well, that's a pity, but that sort of issue arises absolutely all the time and there ought to be better solutions than changing the syntax. Do we need a new syntax to replace LET, with explicit end-of-scope markers? A new syntax for function call, so you can't get confused about what function any given thing is an argument to? That way lie XML and madness...
-- Gareth McCaughan Gareth.McCaug...@pobox.com .sig under construc
> (format t "There ~[are~;is~:;are~] ~:*~d ~a~2:*~p here." 1 'ball)
and challenged:
> Can you read it and tell me without trying it whether it works or not? Give > it to a co-worker who hasn't seen this thread and ask him/her what it prints? > How long until you get an answer?
> There's no question that the format string is more concise. But is using it > better? Is writing code with the least number of characters always best or > is it important to write code that others can read and understand without > reaching for the Lisp manual.
I think John may be missing Kent's key point.
When you write something using FORMAT, it may be harder to read. But, by being concise, it renders the surrounding code easier to read. The code Kent cited is certainly easier to check than John's version using FORMAT; but embed it, along with a few similar fragments, into something else, and *the whole of the something-else will become hard to read*. This is a worthwhile trade-off only if the details of the output are more important than the computation in which it's embedded.
Even if using obscure features of FORMAT is deemed evil, it's possible to improve considerably on the fully explicit code Kent posted by using FORMAT more simply.
(format t "~&There ~A ~D ~A~A here." (if (= n 1) "is" "are") n name (if (= n 1) "" "s"))
Incidentally, John's code is not equivalent to Kent's; it's missing an equivalent of the (TERPRI) at the start. I think this is an indication that fully-written-out code is not as much easier to read as one might think.
-- Gareth McCaughan Gareth.McCaug...@pobox.com .sig under construc
Lieven Marchand <m...@wyrd.be> writes: > Kent M Pitman <pit...@world.std.com> writes:
> > Boris Schaefer <bo...@uncommon-sense.net> writes: > > > As long as we're on that topic: how can you have a (simple) loop that > > > maximizes return a compound structure instead of the maximized value?
> > It's unfortunate that the ability to define other loop-paths was omitted > > from the standard, since most implementations have such a thing.
> I agree to the point that I load the MIT-LOOP code in Lispworks and > shadow CL:LOOP and CL:LOOP-FINISH, but I don't see how add-loop-path > can be used to solve this problem.
Hmm. I haven't looked recently. You're probably right that add-loop-path per se can't. I was speaking more generally to the issue of adding loop keywords, by which I also meant different accumulators. If the loop path stuff couldn't do that, I was in error. Consider my remarks to be revised to say just that I think it's unfortunate that LOOP isn't extensible.