From: Antonio Menezes Leitao
Subject: Loop for and behaviour
Date: 
Message-ID: <pan.2004.05.24.00.09.22.27474@evaluator.pt>
Hi,

After seeing so many complains regarding one of my challenges for
translation from Common Lisp to Java I decided to investigate it.  I
must warn that I'm not a loop expert and all my loops were (and are)
quite simple so I might be wrong about all this.  I hope someone can
clarify the issue for me.  The challenge in question is the following:

(defun test5 ()
  (let ((x 10))
    (loop for x from 1 to 9 
          and y = x then x
          sum (+ x y))))

I was under the impression that the 'and' loop keyword would cause x and y
to be bound in parallel.  In fact, the Hyperspec says:

 6.1.1.5.1 Summary of Variable Initialization and Stepping Clauses

 The for and as constructs provide iteration control clauses that
 establish a variable to be initialized. for and as clauses can be
 combined with the loop keyword and to get parallel initialization and
 stepping[1]. Otherwise, the initialization and stepping[1] are sequential.

"Parallel initialization and stepping" sounded just like the difference
between 'do' and 'do*'.  And I confirmed this idea when I read:

 6.1.2.1 Iteration Control

 If multiple iteration clauses are used to control iteration, variable
 initialization and stepping[1] occur sequentially by default. The and
 construct can be used to connect two or more iteration clauses when
 sequential binding and stepping[1] are not necessary. The iteration
 behavior of clauses joined by and is analogous to the behavior of the
 macro do with respect to do*. 

Not being a loop expert, this made me think that the above loop should
have been identical to the following:

(defun test5 ()
  (let ((x 10))
    (let ((sum 0))
      (do ((x 1 (1+ x))
           (y x x))
          ((> x 9) sum)
        (incf sum (+ x y))))))

However, everybody was complaining that the outer 'x' was an unused
variable. I tried my example in a lot of Common Lisps and, in fact, they
all agreed with everybody's opinion (except mine).  Where was my error?

After further reading the Hyperspec, I found the following possible
explanation:

 6.1.1.4 Expanding Loop Forms

 Implementations can interleave the setting of initial values with the
 bindings. However, the assignment of the initial values is always
 calculated in the order specified by the user. A variable is thus
 sometimes bound to a meaningless value of the correct type, and then
 later in the prologue it is set to the true initial value by using setq.
 One implication of this interleaving is that it is
 implementation-dependent whether the lexical environment in which the
 initial value forms (variously called the form1, form2, form3, step-fun,
 vector, hash-table, and package) in any for-as-subclause, except
 for-as-equals-then, are evaluated includes only the loop variables
 preceding that form or includes more or all of the loop variables; the
 form1 and form2 in a for-as-equals-then form includes the lexical
 environment of all the loop variables.

Note the exception made of the for-as-equals-then.  It looks like the
outer 'x' is, after all, unused, because the 'y' is within the scope
of the inner 'x'.  That's odd.  I couldn't understand the rational for
that behaviour so I digged a little further and I found the long
discussion of the Issue
LOOP-INITFORM-ENVIRONMENT:PARTIAL-INTERLEAVING-VAGUE but after reading it
I became even more confused because it seems that it agrees with me.  They
even include the following example as a test case:

(let ((list '(1 2 3)))
  (loop for list = list then (cdr list)
        until (null list)
        collect (car list)))
=> (1 2 3)

If you try this, I bet your Common Lisp implementation will warn that the
outer 'list' is unused and then the result is NIL and not (1 2 3).

I noted that the Issue status is 'For X3J13 consideration' but I couldn't
see if it was accepted or rejected.  I presume it was rejected.

So, I remain puzzled.  Is there a rational for the 6.1.1.4 Expanding Loop
Forms paragraph that I presented above?

Thanks in advance,

Antonio Leitao. 

From: David Steuber
Subject: Re: Loop for and behaviour
Date: 
Message-ID: <871xlauww3.fsf@david-steuber.com>
Antonio Menezes Leitao <··············@evaluator.pt> writes:

> I became even more confused because it seems that it agrees with me.  They
> even include the following example as a test case:
> 
> (let ((list '(1 2 3)))
>   (loop for list = list then (cdr list)
>         until (null list)
>         collect (car list)))
> => (1 2 3)
> 
> If you try this, I bet your Common Lisp implementation will warn that the
> outer 'list' is unused and then the result is NIL and not (1 2 3).

I decided to try this just to confirm your bet:

$ sbcl
This is SBCL 0.8.10.48, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

* (let ((list '(1 2 3)))
  (loop for list = list then (cdr list)
        until (null list)
        collect (car list)))
; in: LAMBDA NIL
;     (LET ((LIST '(1 2 3)))
;     (LOOP FOR LIST = LIST THEN (CDR LIST) UNTIL (NULL LIST) COLLECT ...))
; 
; caught STYLE-WARNING:
;   The variable LIST is defined but never used.
; compilation unit finished
;   caught 1 STYLE-WARNING condition

NIL

I think what is happening here is that the loop is binding its own
list inside the block defined by loop, shadowing the list declared in
let.

* (loop for list = (list 1 2 3) then (cdr list)
        until (null list)
        collect (car list))

(1 2 3)

* (macroexpand-1 '(let ((list '(1 2 3)))
  (loop for list = list then (cdr list)
        until (null list)
        collect (car list))))

(LET ((LIST '(1 2 3)))
  (LOOP FOR LIST = LIST THEN (CDR LIST) UNTIL (NULL LIST) COLLECT (CAR LIST)))
NIL

LET is a special form.  Oh well.  But...

* (macroexpand-1 '(loop for list = (list 1 2 3) then (cdr list)
        until (null list)
        collect (car list)))

(BLOCK NIL
  (LET ((LIST NIL))
    (SB-LOOP::WITH-LOOP-LIST-COLLECTION-HEAD
     (#:LOOP-LIST-HEAD-3032 #:LOOP-LIST-TAIL-3033)
     (SB-LOOP::LOOP-BODY NIL
                         (NIL (SB-LOOP::LOOP-REALLY-DESETQ LIST (LIST 1 2 3))
                              NIL
                              NIL
                              (WHEN (NULL LIST) (GO SB-LOOP::END-LOOP)))
                         ((SB-LOOP::LOOP-COLLECT-RPLACD
                           (#:LOOP-LIST-HEAD-3032 #:LOOP-LIST-TAIL-3033)
                           (LIST (CAR LIST))))
                         (NIL (SB-LOOP::LOOP-REALLY-DESETQ LIST (CDR LIST))
                              NIL
                              NIL
                              (WHEN (NULL LIST) (GO SB-LOOP::END-LOOP)))
                         ((RETURN-FROM NIL
                            (SB-LOOP::LOOP-COLLECT-ANSWER
                             #:LOOP-LIST-HEAD-3032)))))))
T

Granted this is based on the LOOP implementation used by SBCL, but
that outer BLOCK with a LET setting LIST to NIL seems to explain what
is going on with your first example.  There is shadowing after all.

-- 
I wouldn't mind the rat race so much if it wasn't for all the damn cats.
From: Antonio Menezes Leitao
Subject: Re: Loop for and behaviour
Date: 
Message-ID: <pan.2004.05.24.08.56.29.104586@evaluator.pt>
On Mon, 24 May 2004 03:06:36 -0400, David Steuber wrote:

> Antonio Menezes Leitao <··············@evaluator.pt> writes:
> 
>> I became even more confused because it seems that it agrees with me.  They
>> even include the following example as a test case:
>> 
>> (let ((list '(1 2 3)))
>>   (loop for list = list then (cdr list)
>>         until (null list)
>>         collect (car list)))
>> => (1 2 3)
>> 
>> If you try this, I bet your Common Lisp implementation will warn that the
>> outer 'list' is unused and then the result is NIL and not (1 2 3).
> 
> I decided to try this just to confirm your bet:
> 
> [...]
> 
> Granted this is based on the LOOP implementation used by SBCL, but
> that outer BLOCK with a LET setting LIST to NIL seems to explain what
> is going on with your first example.  There is shadowing after all.

I understand what is going on (and even I quoted the Hyperspec to justify all
Common Lisp implementations (except the not-so-Common-Lispy Linj)).

What I would like to see explained is the rational for the decision to
put the for-as-equals-then subclause inside the lexical scope of all
the loop variables.

It even makes the code look strange. Consider the following example:

(let ((x -1))
  (loop for y = x then x
        and x = 0 then (+ x 5)
	until (> x 10)
	do (print (list x y))))

I bet that if I ask non-loop-experts to translate it to a do-form,
most people would write it as:

(let ((x -1))
  (do ((y x x)
       (x 0 (+ x 5)))
      ((> x 10))
    (print (list x y))))

This translation makes me expect to see the following being printed:

(0 -1)
(5 0)
(10 5)

Instead, I get (from the original loop):

; Note: Variable X defined but never used.

(0 NIL) 
(5 0) 
(10 5)

We can make things even less natural by requiring sequencial binding
(and iteration) and including a reference to the previous binding of
y:

(let ((x -1))
  (loop for y = x then x
        for x = (1+ y) then (+ x 5)
	until (> x 10)
	do (print (list x y))))

I would expect to see:

(0 -1) 
(5 0) 
(10 5)

but I just get an error.

Again, what is the rational?

Thanks,

Antonio Leitao.
From: John Thingstad
Subject: Re: Loop for and behaviour
Date: 
Message-ID: <opr8i4bxzvpqzri1@mjolner.upc.no>
loop uses tagbody and go. (for all iteration. Never do.)


On Mon, 24 May 2004 09:56:32 +0100, Antonio Menezes Leitao  
<··············@evaluator.pt> wrote:

> On Mon, 24 May 2004 03:06:36 -0400, David Steuber wrote:
>
>> Antonio Menezes Leitao <··············@evaluator.pt> writes:
>>
>>> I became even more confused because it seems that it agrees with me.   
>>> They
>>> even include the following example as a test case:
>>>
>>> (let ((list '(1 2 3)))
>>>   (loop for list = list then (cdr list)
>>>         until (null list)
>>>         collect (car list)))
>>> => (1 2 3)
>>>
>>> If you try this, I bet your Common Lisp implementation will warn that  
>>> the
>>> outer 'list' is unused and then the result is NIL and not (1 2 3).
>>
>> I decided to try this just to confirm your bet:
>>
>> [...]
>>
>> Granted this is based on the LOOP implementation used by SBCL, but
>> that outer BLOCK with a LET setting LIST to NIL seems to explain what
>> is going on with your first example.  There is shadowing after all.
>
> I understand what is going on (and even I quoted the Hyperspec to  
> justify all
> Common Lisp implementations (except the not-so-Common-Lispy Linj)).
>
> What I would like to see explained is the rational for the decision to
> put the for-as-equals-then subclause inside the lexical scope of all
> the loop variables.
>
> It even makes the code look strange. Consider the following example:
>
> (let ((x -1))
>   (loop for y = x then x
>         and x = 0 then (+ x 5)
> 	until (> x 10)
> 	do (print (list x y))))
>
> I bet that if I ask non-loop-experts to translate it to a do-form,
> most people would write it as:
>
> (let ((x -1))
>   (do ((y x x)
>        (x 0 (+ x 5)))
>       ((> x 10))
>     (print (list x y))))
>
> This translation makes me expect to see the following being printed:
>
> (0 -1)
> (5 0)
> (10 5)
>
> Instead, I get (from the original loop):
>
> ; Note: Variable X defined but never used.
>
> (0 NIL)
> (5 0)
> (10 5)
>
> We can make things even less natural by requiring sequencial binding
> (and iteration) and including a reference to the previous binding of
> y:
>
> (let ((x -1))
>   (loop for y = x then x
>         for x = (1+ y) then (+ x 5)
> 	until (> x 10)
> 	do (print (list x y))))
>
> I would expect to see:
>
> (0 -1)
> (5 0)
> (10 5)
>
> but I just get an error.
>
> Again, what is the rational?
>
> Thanks,
>
> Antonio Leitao.



-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
From: Antonio Menezes Leitao
Subject: Re: Loop for and behaviour
Date: 
Message-ID: <pan.2004.05.25.07.42.39.567830@evaluator.pt>
On Tue, 25 May 2004 02:15:23 +0200, John Thingstad wrote:

> loop uses tagbody and go. (for all iteration. Never do.)
> 

Could you explain how can the use of tagbody and do be the rationale for
the behaviour of loop?

I must confess I don't see any implication from the use of tagbody and go.
The loop variables must still be established using let (or let*) and the
updates must be done using psetq (or setq).  If you expand a do-form,
you'll also find tagbody and go.

Or am I missing something?

Thanks,

Antonio Leitao.
From: Christian Hofer
Subject: Re: Loop for and behaviour
Date: 
Message-ID: <c90895$job$1@online.de>
John Thingstad wrote:
> loop uses tagbody and go. (for all iteration. Never do.)

Guess, what "do" uses... (at least in SBCL)

The described situation is for me a very strong argument in favour of 
Paul Graham's point of view that the loop macro should be used with caution.

Chris
From: Antonio Menezes Leitao
Subject: Re: Loop for and behaviour
Date: 
Message-ID: <pan.2004.05.26.11.35.55.997930@evaluator.pt>
I want to see the light.

I ask all the members of the Common Lisp community, specially those
who already reached the immortal status (Pitman, Jonl, Fahlman,
Gabriel, Steele, Graham, and Norvig, just to name a few) to help me
see the light.

Please, please, hear my pray.  I'm about to sin...If I don't get an
answer, I will disrespect the 6.1.1.4 commandment.

Antonio Leitao.
From: Rob Warnock
Subject: Re: Loop for and behaviour
Date: 
Message-ID: <gKKdnVFKJ-egtyjdRVn-uA@speakeasy.net>
Antonio Menezes Leitao  <··············@evaluator.pt> wrote:
+---------------
| Please, please, hear my pray.  I'm about to sin...If I don't get an
| answer, I will disrespect the 6.1.1.4 commandment.
+---------------

The standard is as it is, nothing can change that at this point.
As long as you don't break any code that conforms with the standard,
you have wide latitude to do as you wish. As it says in CLHS 6.1.1.4:

    ...it is implementation-dependent whether the lexical environment
    in which the initial value forms (variously called the form1,
    form2, form3, step-fun, vector, hash-table, and package) in any
    for-as-subclause, except for-as-equals-then, are evaluated includes
    only the loop variables preceding that form or includes more or all
    of the loop variables;

Conforming programs will never depend on any particular choice that
the implementor made here. But beware:

    [The] form1 and form2 in a for-as-equals-then form includes the
    lexical environment of all the loop variables.

Conforming programs *may* depend on this latter clause, so don't break that.


-Rob

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