Subject: Re: The LOOP macro (was Re: Be afraid of XML)
From: rpw3@rpw3.org (Rob Warnock)
Date: Sat, 13 Mar 2004 07:31:22 -0600
Newsgroups: comp.lang.lisp
Message-ID: <L-CdnSfcbLu3kM7d3czS-g@speakeasy.net>
David Steuber  <david.steuber@verizon.net> wrote:
+---------------
| The pages on the LOOP facility have some examples but I haven't groked
| the thing yet.  Sure, the simple form is, well, simple.  What I do not
| yet "get" is all the ways I can combine clauses in the extended form.
+---------------

I am, I like to think, by now reasonably comfortable with many of the
idioms of LOOP, e.g., the extremely useful destructuring of list items
into loop variables, and when you want to do that with IN versus ON,
and I tend to write fairly complex LOOPs with great success. Yet I still
get surprised occasionally when I think I'm doing "the right thing" and
it doesn't work.

For example, last night I was reading the NIST page on their Internet Time
Service[1], so just for fun I whipped together a small CMUCL script to:

1. print the local time,
2. grab the time from a random one of the NIST servers, and
3. print the local time again.

Here we see that local time is two seconds slow (oops!):

    % date-nist
    Sat Mar 13 04:58:45 2004   Local time
    Sat Mar 13 04:58:47 2004   nist1.datum.com
    Sat Mar 13 04:58:45 2004   Local time
    %

But my first cut at it contained the following -- note the AND:

    (defun fetch-time/rfc868 (host)
      (let* ((fd (connect-to-inet-socket host 37))
	     (stream (system:make-fd-stream fd
		      :element-type '(unsigned-byte 8))))
	(with-open-stream (s stream)
	  (loop for i below 4
		AND time = (read-byte s) then (+ (read-byte s) (* time 256))
	    finally (return time)))))

which when run fails as follows:

    > (fetch-time/rfc868 "time-c.timefreq.bldrdoc.gov")
    End-of-File on #<Stream for descriptor 6>
    Restarts:
      0: [ABORT] Return to Top-Level.
    ...

Replacing the AND with another FOR fixes the problem:

    > (defun fetch-time/rfc868 (host)
	(let* ((fd (connect-to-inet-socket host 37))
	       (stream (system:make-fd-stream fd
			:element-type '(unsigned-byte 8))))
	  (with-open-stream (s stream)
	    (loop for i below 4
		  AND time = (read-byte s) then (+ (read-byte s) (* time 256))
	      finally (return time)))))

    FETCH-TIME/RFC868
    > (fetch-time/rfc868 "time-c.timefreq.bldrdoc.gov")

    3288171947
    >

Now at first blush, it seemed that in this particular instance the
choice of AND (parallel binding) versus FOR (sequential binding)
*shouldn't* have made a difference, since neither term uses the
other's variable... but it did. Wow, magic. (*Whoo-EEE-ooo.*)

But upon further reflection, I figured out that it was doing exactly
as it's supposed to: When you have parallel variable bindings/steppings
(using AND), any loop termination tests are done *after* all of the
parallel variables have been stepped, whereas with sequential binding (FOR),
each termination test is done in order, and a later (inner) variable
isn't stepped (or tested) at all if the LOOP terminates due to an outer
variable limit. That is:

    > (loop for i below 4
            and j below 4
       finally (return (list i j)))

    (4 4)
    > (loop for i below 4
            for j below 4
       finally (return (list i j)))

    (4 3)
    >

Oh. Duh.

LOOP is definitely one of the parts of CL where you learn something new
every day...  ;-}  ;-}


-Rob

[1] <URL:http://www.boulder.nist.gov/timefreq/service/its.htm>

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607