[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

loop macro



It sounds like what you're really saying isn't that you dislike keywords
in all cases, but that you dislike keywords when they aren't the first
element of a list.  The main reason you cite is that it's inconsistent
with the rest of the language, which never does that.

This is a pretty good argument and it's mostly true.  On the other hand,
I'm not sure it's completely true.  Compare

(reduce #'+ seq :start 5 :end 7)

and

(loop for a from 4 below 7 do ..body..)

I'm not sure they're really all that different.  The fact that LOOP uses
infix keywords, per se, doesn't make LOOP that different from the rest
of the language.

The thing that's more upsetting is

(loop for a from 4 below 7
      for b from 7 do ..body..)

because there is a clear grouping between the elements in the first
"for" clause and the elements in the second "for" clause, but that
grouping is not expressed by parentheses.  I think this is where the
existing LOOP starts to look inconsistent with the syntax of the rest of
the language.  Because you've mentioned "little words as delimiters" in
some of your mail, I suspect that this is the real point of contention.
I agree that this is a drawback to the existing LOOP.

The most obvious way to try to repair this is to make the syntax look
more like DO, by having a bunch of iteration-specifying constructs at
the front of the form, followed by a body.  This might look like the
schemes we've seen in recent mail.

However, this severely restricts the power of the construct.  Here's the
most basic and obvious example that comes to my mind.  One of our basic
requirements for LOOP was that it should be able to support the popular
repeat/until construct present in many of the Pascal-family languages,
in which the checking is done at the end of the loop rather than at the
beginning.  For clarity, we felt that the predicate form ought to appear
syntactically following the body.

It's possible that this should be handled by simply telling the program
to write a (when <predicate> (return <form>)) in the body.  We didn't
like this solution, because it seemed too much like a step backward from
DO back towards PROG.  Part of the idea of DO was to get the basic
iteration control constructs separate from the body.  After all, we
don't need the end-test part of DO at all; a similar when/return at the
beginning of the body would have the same effect.

We could add grouping without going so far as to force all clauses
to the front.

(loop (for a from 4 below 7)
      (for b from 7)
      (do ..body..)
      (until ..pred..))

I think this might be an improvement.  One problem is that we were
hoping to get rid of the "do" keyword entirely (Moon agrees with this),
but with this change, it's harder to see how to accomplish that.

Your argument about finding it "hard to remember which little words" is
something I can only partially agree with.  One might just as well point
out that Common Lisp has many functions and special forms, and it's
often hard to remember their names.  This sounds to me like a question
of the programming environment and the organization of the
documentation, more than a question of language design.

There's another LOOP feature that I consider quite valuable, and I also
think that the clauses-up-front-and-a-body syntax might be hard to
extent to handle.  This is the "always" keyword and its friends.

(loop for e in list always <predicate-form-using-e>)

is more expressive than

(not (dolist (e list)
       (when <predicate-form-using-e>
	 (return t))))

or

(not (find-if-not #'(lambda (e) <predicate-form-using-e>) list)

and it's also more general, because the range of iteration that LOOP can
express is so much more than just iterating over one sequence.

ALWAYS is strange because the predicate form is really the body, in some
sense, but we aren't just executing it for effect over and over.  This
is why I'm not sure it fits into the clauses-up-front structure so well;
I think it would be sort of strange for the predicate form to live in
one of the clauses-up-front and for the body to be empty.

Since I'm getting into details, I should make quite clear that I do not
speak for Moon in the matter of LOOP.  Our opinions on this topic
diverge much more than they do on most other topics.

One of my own strong feelings about LOOP that Moon does not share is
that we should get rid of the conditionals.  I think Common Lisp has at
least enough basic conditional forms as it is, and having separate ones
for LOOP is beyond the pale.  I am particularly grossed out by the idea
of having a "dangling ELSE" in Lisp.  The present LOOP, like the
conventional languages, has the convention that such an ELSE binds to
the inner IF.  This fits into Lisp just about as much as the concept of
"operator precedence", i.e. not at all.

The main reason for the conditionals is so that COLLECT clauses can be
executed conditionally. (I don't know for sure whether there are other
reasons for the conditionals.)  The usual proposed solution is to make
COLLECT a regular Lisp macro, that communicates with an outer collector
macro via COMPILER-LET, making COLLECT a separate facility completely
orthogonal to LOOP.  It seems clear to me that this is the right thing
to do.  After all, suppose you want to write a program that does a
double recursion down a binary tree, and collects something as it goes?
LOOP can't handle this kind of iteration, but you still want COLLECT.
Yes, I know you can write your own LOOP iteration path, but what if you
think that nice, traditional, Lispy recursion is a simple and expressive
way to write your program?

These are just some of the traditional issues of LOOP redesign.  There
are definitely others.  I just wanted to give everyone some idea of the
kinds of things we've thought about.