-ted
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
(let foo ((l list)
(result '()))
(if (null? l)
(reverse! result)
(foo (cdr l) (cons (f (car l)) result))))
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.,
(let ((i 0))
(do-external-symbols (s "COMMON-LISP")
(incf i))
i) => 978
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
(collecting (result :type list)
(dolist (x y)
(when (f x)
(collect-into result (g x)))))
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...
Hope that helps.
| 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?
Something like:
(defclass foo ()
((name :accessor name)
(weight :accessor weight)))
(let ((foo1 (make-instance 'foo :name "foo1" :weight 10))
(foo2 (make-instance 'foo :name "foo2" :weight 9)))
(loop for foo in (list foo1 foo2)
maximize (weight foo))
This will return 10. I want it to return foo1. Is there some easy
way to accomplish this with maximize?
--
bo...@uncommon-sense.net - <http://www.uncommon-sense.net/>
The opposite of a correct statement is a false statement. But the opposite
of a profound truth may well be another profound truth.
-- Niels Bohr
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."
The original is there : http://www.paulgraham.com/popular.html
OK, a last funny one:
"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. "
Sigh...
Marc
> 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?
>
> Something like:
>
> (defclass foo ()
> ((name :accessor name)
> (weight :accessor weight)))
>
> (let ((foo1 (make-instance 'foo :name "foo1" :weight 10))
> (foo2 (make-instance 'foo :name "foo2" :weight 9)))
> (loop for foo in (list foo1 foo2)
> maximize (weight foo))
>
> 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
(let ((best-foo nil)) ;untested
(dolist (foo (list foo1 foo2))
(when (or (not best-foo)
(> (weight foo) (weight best-foo)))
(setq best-foo foo)))
best-foo)
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.
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
franz inc.
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.
ciao,
Jochen
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.
> 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.
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/ )
> 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).
Cheers,
Christophe
--
Jesus College, Cambridge, CB5 8BL +44 1223 510 299
http://www-jcsu.jesus.cam.ac.uk/~csr21/ (defun pling-dollar
(str schar arg) (first (last +))) (make-dispatch-macro-character #\! t)
(set-dispatch-macro-character #\! #\$ #'pling-dollar)
> In article <9mb3r4$t1q$1...@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 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.
-john foderaro
franz inc
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))))))
> 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.
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?
Coby
--
(remove #\space "coby . beck @ opentechgroup . com")
"Ted Sandler" <tedsa...@rcn.com> wrote in message
news:3B88815A...@rcn.com...
...
> 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'.
> (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/iter/iterate/0.html>?
(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.
--
Hannu
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.
///
> 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.M...@pobox.com
.sig under construc
> (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.|))
and a typical equivalent using FORMAT
> (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.
> 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.
> 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.
One historical reason for this is that in some Lisp dialects (Lisp
Machin Lisp, I believe, and maybe others) there was an IF that took
(IF test then &body else)
so there the back-indentation helped you know when you were in the "body".
You were supposed to put your "short" side of the test first.
Another reason is that people get confused when they see:
(if (a b c d) (e f (g h) (i j (k l)) (m n)) (o p (q r) (s t)))
as to where the THEN and ELSE are. So if you put your cursor after the
IF in emacs and do C-M-f LINEFEED C-M-f LINEFEED you'll get the right
indentation AND it will be "obvious" to someone reading that line 3
of the new expression ("programs count from 0, people count from 1")
begins the else part. AND moreover, you can check the "correctness"
of this "visual claim" by pressing TAB on that line and seeing that
the line does not move.
(if (a b c d)
(e f (g h) (i j (k l)) (m n))
(o p (q r) (s t)))
> It would almost make sense if the "else" part of an IF had an
> implicit PROGN, but it doesn't.
Historically, it was. And people didn't want a billion different lisp modes.
Although if you look at my code, you'll see that it always starts with
;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Package: CL-USER; -*-
(in-package "CL-USER")
both because the Syntax info has programmatic effect on Symbolics (selecting
a sub-mode of Lips mode) and because it's a nice declarative piece of info.
(All of that notwithstanding, though, I still prefer the balanced indenting
of if, and I am constantly nudging those little else parts back over to be
lined up underneath the then part in my own code when using Emacs because
I've been too lazy to work out the init-file setup to make it indent in
the way I think of as "right".)
That's pretty funny, given how happy you are to be a part of usenet's
collective Lisp knowledge, that you don't try to take advantage of
usenet's emacs knowledge. I guess it's one of those things you only
think of when you're about to hit `space', which means you're in the
middle of something more important. Try adding this to your .emacs
file, see how you like it:
(add-hook 'lisp-mode-hook
#'(lambda ()
(setq lisp-indent-function 'common-lisp-indent-function)))
I was just trying to go along with the point you were making: format introduces a new
language just for controlling I/O. So I used that language to its fullest extent.
Imagine if 'n' and 'name' in your example were computed values. You would have to
change your code to let bind variables to the computation of those values. I wouldn't
have to change a thing. I thought that's the point you were getting at when you
mentioned the power of an imbedded I/O language (which effectively has a let form
since you can go back to certain argument values repeatedly).
Your use of format is so simple that there are plenty of other ways to do what you've
done that don't require an imbedded I/O language. Back in the old Franz Lisp days I
had a 'msg' macro that expanded into a sequence of print's of its arguments. So I
would write something like
(msg :newline "There " (if* (= n 1) then "are" else "is") " " n " "
name (if* (not (= n 1)) then "s" else "") " here.")
This has too much code but one could imagine extending the msg macro so you could
write
(msg :newline "There " (:bool (= n 1) "are" "is") " " n " " name (:plural n) " here.")
The advantage here is that it stays all in Lisp. I could do real lisp iteration
in the middle of the stuff being printed and then revert back to printing mode inside
the iteration using the msg macro again (for anyone who has used the htmlgen html
printer inside AllegroServe it's the same idea).
> That's pretty funny, given how happy you are to be a part of usenet's
> collective Lisp knowledge, that you don't try to take advantage of
> usenet's emacs knowledge. I guess it's one of those things you only
> think of when you're about to hit `space', which means you're in the
> middle of something more important. Try adding this to your .emacs
> file, see how you like it:
> (add-hook 'lisp-mode-hook
> #'(lambda ()
> (setq lisp-indent-function 'common-lisp-indent-function)))
Heh. That's true about when you feel the aggravation and how quickly
it passes. But also, the other thing that keeps me from doing it is
the knowledge I have the fix in any of seventeen files already locally
on my disk--just misplaced. (I don't think this is the fix I have; I
think I have a specific list of indent rules.) But I hate asking
questions I already have the answer to--knowing that it generates work
for someone to post a response to a question, I try to keep my
questions to a minimum... But since you volunteer the info, sure,
I'll give it a go. Thanks. :-)