From: Kjeld Larsen
Subject: Loop macro
Date: 
Message-ID: <C4p52G.13K@stl.dk>
To conclude the discussion of the loop-macro let us present
the following piece of code written by John Burger and found 
in the group comp.lang.clos:

(defun least-common-superclass (list-o-instances)
  (loop with candidates = (reduce #'intersection
                                  (mapcar #'(lambda (i)
                                              (clos:class-precedence-list
                                               (class-of i)))
                                          list-o-instances))
        with best-candidate = (find-class 'T)
        for (current-candidate . remaining-candidates) on candidates
        when (and (subtypep current-candidate best-candidate)
                  (every #'(lambda (other-candidate)
                             (subtypep current-candidate other-candidate))
                         remaining-candidates))
          do (setf best-candidate current-candidate)
        finally (return best-candidate)))

The code segment reveals that the programmer masters mapping and
lambdas, but it is not quite clear why that coding style is mixed
with the loop-style. Below is 'the same' code, slightly modified for
Lisp-style (sorry):

(defun least-common-superclass (instances)
  (let ((candidates (reduce #'intersection
                      (mapcar #'(lambda (instance)
                                  (clos:class-precedence-list 
                                    (class-of instance)))
                              instances)))
        (best-candidate (find-class t)))

    (mapl #'(lambda (candidates)
              (let ((current-candidate (first candidates))
                    (remaining-candidates (rest candidates)))
                (when (and (subtypep current-candidate best-candidate)
                           (every #'(lambda (remaining-candidate)
                                      (subtypep current-candidate 
                                                remaining-candidate))
                                  remaining-candidates))
                      (setf best-candidate current-candidate))))
          candidates)

    best-candidate))

(Hope it's the same, it hasn't been tested)

One good reason to include the loop-macro in the standard has been mentioned:
it was there and it was in need on standardization. Other parts of Common Lisp
are there for the same reason. (Was it Steele who said that people come
to take pictures of Common Lisp, like of an Old English farm-house?)

And one good reason to use it has been mentioned (by several people):
'I' know it and it is simple to use.
Perhaps we don't feel like that. One main objection to the macro that always
will remain is that the pure and simple syntax of Lisp is polluted by things
like loop; indeed, Lisp-like languages are the only programming languages
that do have simple syntaxes (does this word have a plural form in English?)
and it's a shame to corrupt it with a more complex syntax.

To all godforsaken users of the loop-macro, the message is clear:

Write Lisp programs like they are written in the excellent book
"Structure and Interpretation of Computer Programs" by Abelson and Sussman.
Even though it is a book on Scheme there are beautiful examples
and principles that apply to all dialects of Lisp (and to other programming
languages as well.) It contains real programs, and there is nothing at all like
loop in it.

- Kjeld & Flemming

From: Chuck Fry
Subject: Re: Loop macro
Date: 
Message-ID: <1993Mar31.185723.4219@kronos.arc.nasa.gov>
I know it's not wise to butt in to a religious discussion, but I'm not
always wise.

There are complex iteration paradigms which are writable in LOOP that
are not possible with any other standard Lisp form, with the sole
exception of PROG.  In my usage, these paradigms typically involve a
combination of scanning a sequence and operating on the results of that
scan.  Implementing them in terms of the more "Lispish" sequence
utilities would be either costly in space or time, or at least as
unclear as the LOOP form.

My religion forbids the use of PROG, and discourages the use of DO or
DO* where it obfuscates the intent of the code.  I have no qualms about
using FLET or LABELS where appropriate, nor any of the mapping
functions.  I just happen to think that in some cases, LOOP is the most
aesthetic (or least ugly) way of getting a particular job done.

 -- Chuck Fry, professional Lisper for 9 1/2 years
-- 
     Chuck Fry  ······@freud.arc.nasa.gov  ······@ptolemy.arc.nasa.gov
   Join the Silicon Valley protest against 55 MPH!  Email me for details.
	    I alone bear responsibility for the claptrap above.
From: Chris Riesbeck
Subject: Re: Loop macro
Date: 
Message-ID: <1pf99k$78o@anaxagoras.ils.nwu.edu>
In article <··········@stl.dk>, ···@stl.dk (Kjeld Larsen) writes:
> 
> To conclude the discussion of the loop-macro let us present
> the following piece of code written by John Burger and found 
> in the group comp.lang.clos:
>
> ... [original code deleted] ...
>
> The code segment reveals that the programmer masters mapping and
> lambdas, but it is not quite clear why that coding style is mixed
> with the loop-style. Below is 'the same' code, slightly modified for
> Lisp-style (sorry):
> 
> (defun least-common-superclass (instances)
>   (let ((candidates (reduce #'intersection
>                       (mapcar #'(lambda (instance)
>                                   (clos:class-precedence-list 
>                                     (class-of instance)))
>                               instances)))
>         (best-candidate (find-class t)))
> 
>     (mapl #'(lambda (candidates)
>               (let ((current-candidate (first candidates))
>                     (remaining-candidates (rest candidates)))
>                 (when (and (subtypep current-candidate best-candidate)
>                            (every #'(lambda (remaining-candidate)
>                                       (subtypep current-candidate 
>                                                 remaining-candidate))
>                                   remaining-candidates))
>                       (setf best-candidate current-candidate))))
>           candidates)
> 
>     best-candidate))
> 
> (Hope it's the same, it hasn't been tested)
>
> ... [text on Lisp programming style]... 
>
> - Kjeld & Flemming

As far as I'm concerned, both versions of this function
miss the boat.  Simply replacing LOOP FOR ... ON with MAPL 
doesn't really make a significant improvement in 
readability.

First, here's my version, then my philosophy of coding:

    (defun least-common-superclass (instances)
      (reduce #'more-specific-class
	      (common-superclasses instances)
	      :initial-value (find-class 't)))

    (defun common-superclasses (instances)
      (reduce #'intersection
	      (superclass-lists instances)))

    (defun superclass-lists (instances)
      (loop for instance in instances
	    collect (ccl:class-precedence-list
		     (class-of instance))))

    (defun more-specific-class (class1 class2)
      (if (subtypep class2 class1) class2 class1))


If you don't find this code significantly more readable,
skip to the next article.

The critical difference is not LOOP or mapping functions,
it's following some simple rules of coding.  My primary
rule for readable code is this:

	One function to a function.

That is, one function does one job. This usually means that
each function has one control structure, e.g.,
loop, dispatch, combine, etc.  The "slots" of the
control structure are filled in with simple calls to
other functions.  The names of those functions
document what's happening in a way that anonymous
chunks of code never can.

While you may fear an explosion of useless subfunctions, e.g., 

	(defun first-of-list (l) (first l))

most real code has the opposite problem of not enough
subfunctions.  Everyone wants to cram everything into one
package.

The "one function to a function" rule is intentionally
extreme, and owes a great deal to Strunk and White.  Like
a strict diet, you have to force yourself to stick with
it for the first few weeks. IMHO it's worth it.

Once your functions are down to one control structure
apiece, and that control structure involves repetition,
it often doesn't matter what looping form you pick.

I used LOOP in SUPERCLASS-LISTS, but I could have used
MAPCAR.  When functions are this simple, you can pick
what you like, or what you think is most efficient, and
it will be just as readable.  That goes for mapping, LOOP,
DO, series, etc.

Sometimes, it does matter.  There are limitations
in each kind of looping structure, and it doesn't make
sense to insist on only one kind.

LOOP ... COLLECT vs. MAPCAR -- LAMBDA is a tie for me,
but if you want to collect only certain values, then

	(loop for x in l
	      when <test x>
	      collect x)

is hands-down clearer than

	(mapcan #'(lambda (x) 
		    (when <test x> (list x)))
		l)

while 

	(remove-if-not #'(lambda (x) <test x>) l)

is OK but that double-negative leaves me cold.

On the other hand, when REDUCE is relevant, as in the
above example, it beats the corresponding LOOP FOR = THEN.
Too bad for LOOP supporters that it doesn't have something
like

	(loop for class-list in (superclass-lists instances)
              collect class-list using #'intersection


EXECUTIVE SUMMARY:

Good Lisp coding is like good nutrition:

     Eat light -- One function to a function.
     Eat a variety of foods -- Don't insist on just
	one iteration form.

Chris
-----
From: Jeff Dalton
Subject: Loop macro and least-common-superclass
Date: 
Message-ID: <8559@skye.ed.ac.uk>
We've now seen a couple of definitions of least-common-superclass
that look rather long and complex to me.  Is there something, apart 
from efficiency, wrong with either of the following:

(defun least-common-superclass (instances)
  (let ((class-precedence-lists
	 (mapcar #'(lambda (i)
		     (clos:class-precedence-list (class-of i)))
		 instances)))
    (find-if #'(lambda (candidate-class)
		 (every #'(lambda (cpl)
			    (member candidate-class cpl))
			(rest class-precedence-lists)))
	     (first class-precedence-lists))))

(defun least-common-superclass2 (instances)
  (find-if #'(lambda (candidate-class)
	       (every #'(lambda (instance)
			  (typep instance candidate-class))
		      instances))
	   (clos:class-precedence-list (class-of (first instances)))))

Now, of course there's one problem with this approach, as shown
below:

  (defclass b () ())
  (defclass c () ())
  (defclass a1 (b c) ())
  (defclass a2 (c b) ())

  (setq an-a1 (make-instance 'a1))
  (setq an-a2 (make-instance 'a2))

  > (least-common-superclass (list an-a1 an-a2))
  #<Standard-Class B>

but:

  > (least-common-superclass (list an-a2 an-a1))
  #<Standard-Class C>

But this same problme occurs with the definitions posted earlier
(at least the ones I've seen so far) and with the following:

(defun least-common-superclass3 (instances)
  (first
   (sort (reduce #'intersection
		 (mapcar #'(lambda (i)
			     (clos:class-precedence-list (class-of i)))
			 instances))
	 #'proper-subclass-p)))

(defun proper-subclass-p (c1 c2)
  (and (not (eq c1 c2))
       (subtypep c1 c2)))

-- jd