From: Edi Weitz
Subject: Illegal LOOP usage?
Date: 
Message-ID: <uabypoqdh.fsf@agharta.de>
So, this function from Drakma

  (defun split-string (string &optional (separators " ,-"))
    "Splits STRING into substrings separated by the characters in the
  sequence SEPARATORS.  Empty substrings aren't collected."
    (loop for char across string
          when (find char separators :test #'char=)
          when collector
          collect (coerce collector 'string) into result
          and do (setq collector nil) end
          else
          collect char into collector
          finally (return (if collector
                            (append result (list (coerce collector 'string)))
                            result))))

works as intended with LispWorks, but doesn't work with AllegroCL or
SBCL.  It seems the latter two simply ignore the (SETQ COLLECTOR NIL)
form.  My guess is that the code above is somehow incorrect in that it
modifies a variable which is used in a "collect ... into ..." clause,
but I couldn't find anything in the CLHS that explicitly says so.

Any hints?

Thanks,
Edi.

-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")

From: Tim Bradshaw
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <1173272360.203577.248200@64g2000cwx.googlegroups.com>
On Mar 7, 12:21 am, Edi Weitz <········@agharta.de> wrote:
> So, this function from Drakma
>
>
> works as intended with LispWorks, but doesn't work with AllegroCL or
> SBCL.

I think it's extremely unlikely to be safe to assign to variables into
which you are collecting.  Implementationally collecting obviously has
to keep a hidden tail-pointer (so it doesn't have to repeatedly walk
the list), and you're making assumptions that the implementation will,
each time, check that the variable into which you're collecting hasn't
changed and, if it has, somehow reset its state.  A far more plausible
implementation would just check the tail pointer (initially NIL):

(let ((c nil) (ct nil))
   ...
   (if (null ct)
       (setf ct (cons it nil)
             c ct)
       (setf (cdr ct) (cons it nil)
             ct (cdr ct)))
   ...)

so assigning to C will not do at all what you expect.

I guess an alternative implementation might be to check C:

(let ((c nil) (ct nil))
   ...
   (if (null c)
       (setf ct (cons it nil)
             c ct)
       (setf (cdr ct) (cons it nil)
             ct (cdr ct)))
   ...)

such an implementation would `work' in the special case that you set C
to NIL, but not in any other case.  That might be what you're  seeing.

I have not checked, but I'd hope that the spec does not mandate that
general assignment to C should work.

--tim
From: Marc Battyani
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <0ridnT9LnJ-6I3PYnZ2dnUVZ8smonZ2d@giganews.com>
"Tim Bradshaw" <··········@tfeb.org> wrote

> I think it's extremely unlikely to be safe to assign to variables into
> which you are collecting.  Implementationally collecting obviously has
> to keep a hidden tail-pointer (so it doesn't have to repeatedly walk
> the list), and you're making assumptions that the implementation will,
> each time, check that the variable into which you're collecting hasn't
> changed and, if it has, somehow reset its state.  A far more plausible
> implementation would just check the tail pointer (initially NIL):

I don't know for LOOP but the ITERATE implementation checks if the collected 
value is nil so reseting it to nil is ok but setting it to another value is 
not:

(collect char into collector) =>
(progn
  (setq #:|temp133| (list char))
  (setq #:|end-pointer134|
        (if collector
            (setf (cdr #:|end-pointer134|) #:|temp133|)
            (setq collector #:|temp133|)))
  collector)

> I have not checked, but I'd hope that the spec does not mandate that
> general assignment to C should work.

Yep, the LOOP spec is a fuzzy one. The ITERATE one specifies that the value 
can be used but not set. Maybe it should be modified to add that reseting it 
to nil is OK as it's a common usage . Though generally it's often done with 
push + nreverse.

Marc
From: Edi Weitz
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <uabypdvsm.fsf@agharta.de>
On Wed, 7 Mar 2007 14:29:11 +0100, "Marc Battyani" <·············@fractalconcept.com> wrote:

> I don't know for LOOP but the ITERATE implementation checks if the
> collected value is nil so reseting it to nil is ok but setting it to
> another value is not

Yes, that's basically what CLISP's and LispWorks' LOOP implementations
do as well.

-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")
From: Edi Weitz
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <uejo1dvvi.fsf@agharta.de>
On 7 Mar 2007 04:59:20 -0800, "Tim Bradshaw" <··········@tfeb.org> wrote:

> I think it's extremely unlikely to be safe to assign to variables
> into which you are collecting.  Implementationally collecting
> obviously has to keep a hidden tail-pointer (so it doesn't have to
> repeatedly walk the list), and you're making assumptions that the
> implementation will, each time, check that the variable into which
> you're collecting hasn't changed and, if it has, somehow reset its
> state.

That would support my initial claim that the rationalization of the
current behaviour is just a retroactive justification of the
implementation.

-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")
From: Tim Bradshaw
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <1173279296.182031.151240@8g2000cwh.googlegroups.com>
On Mar 7, 1:30 pm, Edi Weitz <········@agharta.de> wrote:

> That would support my initial claim that the rationalization of the
> current behaviour is just a retroactive justification of the
> implementation.

There are too many long words in this for me to understand it.  But I
think that any non-heroic implementation (where for "heroic" I mean
"wasting far too much time implementing stuff that you should not be
bothering with") will not support assignement (or possibly will not
support assignment other than to NIL).  The standard should make this
clear: if it doesn't that's a bug in the standard.
From: Ken Tilton
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <MeEHh.3035$h73.2150@newsfe12.lga>
Tim Bradshaw wrote:
> On Mar 7, 1:30 pm, Edi Weitz <········@agharta.de> wrote:
> 
> 
>>That would support my initial claim that the rationalization of the
>>current behaviour is just a retroactive justification of the
>>implementation.
> 
> 
> There are too many long words in this for me to understand it.

He said, "Design is an implementation bug that has been defended".

hth, kt

-- 
Well, I've wrestled with reality for 35 years, Doctor, and
I'm happy to state I finally won out over it.
                                   -- Elwood P. Dowd

In this world, you must be oh so smart or oh so pleasant.
                                   -- Elwood's Mom
From: Tim Bradshaw
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <1173361670.575387.7950@30g2000cwc.googlegroups.com>
On Mar 7, 7:19 pm, Ken Tilton <·········@gmail.com> wrote:


> He said, "Design is an implementation bug that has been defended".
>

Ah, well no.  My point was that any non-heroic implementation has
issues like this and the heroic implementations suck for other reasons
(code size, supporting programming styles which have inherently
terrible performance &c).  So the design should be careful not to
require such implementations because those bugs aren't.

--tim
From: Edi Weitz
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <uodn5ccna.fsf@agharta.de>
On 7 Mar 2007 06:54:56 -0800, "Tim Bradshaw" <··········@tfeb.org> wrote:

> The standard should make this clear: if it doesn't that's a bug in
> the standard.

We agree on this point... :)

-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")
From: ········@gmail.com
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <1173358122.600675.294860@n33g2000cwc.googlegroups.com>
On Mar 7, 1:59 pm, "Tim Bradshaw" <··········@tfeb.org> wrote:
> On Mar 7, 12:21 am, Edi Weitz <········@agharta.de> wrote:
>
> > So, this function from Drakma
>
> > works as intended with LispWorks, but doesn't work with AllegroCL or
> > SBCL.
>
> I think it's extremely unlikely to be safe to assign to variables into
> which you are collecting.  Implementationally collecting obviously has
> to keep a hidden tail-pointer (so it doesn't have to repeatedly walk
> the list), and you're making assumptions that the implementation will,
> each time, check that the variable into which you're collecting hasn't
> changed and, if it has, somehow reset its state.  A far more plausible
> implementation would just check the tail pointer (initially NIL):

Actually, there is a technique for doing this efficiently, which I
used in my collection utilities: http://common-lisp.net/pipermail/small-cl-src/2004-October/000041.html

(Incidentally, the web interface to the small-cl-src list completely
ruins the point of the archives by fucking up forms like ,@forms
because it thinks they're e-mail addresses)

I assume Lispworks must be doing something similar (remember, this
does work how Edi expected in one implementation).  The trick is to
use a functional interface to the collector, so (foo 1) collects 1,
(foo) returns the collected list, and (setf (foo) x) reinitializes the
head and tail pointers.  Then wrap that interface with a symbol-macro
and you're done.
From: Tim Bradshaw
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <1173367640.989446.54910@s48g2000cws.googlegroups.com>
On Mar 8, 12:48 pm, ········@gmail.com wrote:

 The trick is to
> use a functional interface to the collector, so (foo 1) collects 1,
> (foo) returns the collected list, and (setf (foo) x) reinitializes the
> head and tail pointers.  Then wrap that interface with a symbol-macro
> and you're done.

That's precisely what I was calling a heroic implementation.  What
should (setf collector (big-function-returning-something)) do?  For
instance, should it walk the (potentially very long) list (if it is a
list) and set the tail pointer?  Now what about (setf collector (cons
1 collector))?  Probably it now walks the list as well.  Probably lots
of people don't expect that and will be mystified about why their code
is now incredibly slow.  Better to just say not to assign to iteration
variables, which also allows an implementation which doesn't use
symbol macros and local functions (or even hairier analysis than LOOP
already needs to do to avoid them).  People who want more correct
collection utilities can probably write them (I think mine probably
predate yours by almost 2 decades :-))

--tim
From: Edi Weitz
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <u4povq1ef.fsf@agharta.de>
On 8 Mar 2007 04:48:42 -0800, ········@gmail.com wrote:

> Actually, there is a technique for doing this efficiently, which I
> used in my collection utilities: http://common-lisp.net/pipermail/small-cl-src/2004-October/000041.html
>
> (Incidentally, the web interface to the small-cl-src list completely
> ruins the point of the archives by fucking up forms like ,@forms
> because it thinks they're e-mail addresses)
>
> I assume Lispworks must be doing something similar (remember, this
> does work how Edi expected in one implementation).  The trick is to
> use a functional interface to the collector, so (foo 1) collects 1,
> (foo) returns the collected list, and (setf (foo) x) reinitializes
> the head and tail pointers.  Then wrap that interface with a
> symbol-macro and you're done.

That's clever, but that's not how LispWorks and CLISP do it.  That the
loop worked for me as expected was more or less by accident.  The only
situation where it really works is the one where you (as in my case)
set the collector to NIL.  See the first (IF COLLECTOR ...) form in
the macro expansion below.


(BLOCK NIL
  (MACROLET ((LOOP-FINISH () '(GO #:|end-loop-16404|)))
    (LET ((#:|across-expr-16407| STRING)
          (#:|across-length-16408| 0)
          (#:|across-counter-16409| 0)
          (CHAR NIL))
      (DECLARE (TYPE VECTOR #:|across-expr-16407|))
      (DECLARE (TYPE FIXNUM #:|across-length-16408|))
      (DECLARE (TYPE FIXNUM #:|across-counter-16409|))
      (LET ((RESULT NIL))
        (DECLARE (TYPE (OR NULL LIST) RESULT))
        (LET ((#:|aux-var-16412| RESULT))
          (LET ((COLLECTOR NIL))
            (DECLARE (TYPE (OR NULL LIST) COLLECTOR))
            (LET ((#:|aux-var-16414| COLLECTOR))
              (TAGBODY (PROGN
                         (LET ((#:|temp-16410| (SYSTEM::ACROSS-CHECK-IS-VECTOR #:|across-expr-16407|)))
                           (SETQ #:|across-length-16408| #:|temp-16410|))
                         (WHEN (OR (= 0 #:|across-length-16408|)) (GO #:|end-loop-16404|))
                         (SETQ CHAR (AREF #:|across-expr-16407| 0)))
               #:|begin-loop-16403| (LET ((LOOP::IT (FIND CHAR SEPARATORS :TEST #'CHAR=)))
                                      (IF LOOP::IT
                                          (PROGN
                                            (LET ((LOOP::IT COLLECTOR))
                                              (IF LOOP::IT
                                                  (PROGN
                                                    (LET ((#:|accum-value-16413|
                                                           (LIST (COERCE COLLECTOR 'STRING))))
                                                      (IF RESULT
                                                          (SETQ #:|aux-var-16412|
                                                                (CDR (RPLACD
                                                                      (THE CONS #:|aux-var-16412|)
                                                                      #:|accum-value-16413|)))
                                                        (PROGN
                                                          (SETQ RESULT #:|accum-value-16413|)
                                                          (SETQ #:|aux-var-16412|
                                                                #:|accum-value-16413|))))
                                                    (SETQ COLLECTOR NIL))
                                                (PROGN))))
                                        (PROGN
                                          (LET ((#:|accum-value-16415| (LIST CHAR)))
                                            (IF COLLECTOR
                                                (SETQ #:|aux-var-16414|
                                                      (CDR (RPLACD (THE CONS #:|aux-var-16414|)
                                                                   #:|accum-value-16415|)))
                                              (PROGN
                                                (SETQ COLLECTOR #:|accum-value-16415|)
                                                (SETQ #:|aux-var-16414| #:|accum-value-16415|)))))))
                       (PROGN
                         (LET ((#:|temp-16411| (THE FIXNUM (1+ #:|across-counter-16409|))))
                           (SETQ #:|across-counter-16409| #:|temp-16411|))
                         (WHEN (OR (= #:|across-counter-16409| #:|across-length-16408|))
                           (GO #:|end-loop-16404|))
                         (SETQ CHAR (AREF #:|across-expr-16407| #:|across-counter-16409|)))
                       (GO #:|begin-loop-16403|)
               #:|end-loop-16404| (RETURN (IF COLLECTOR
                                              (APPEND RESULT (LIST (COERCE COLLECTOR 'STRING)))
                                            RESULT))
                       (RETURN-FROM NIL NIL)))))))))

-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")
From: Madhu
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <m3zm6poodf.fsf@robolove.meer.net>
* Edi Weitz <·············@agharta.de> :

|   (defun split-string (string &optional (separators " ,-"))
|     "Splits STRING into substrings separated by the characters in the
|   sequence SEPARATORS.  Empty substrings aren't collected."
|     (loop for char across string
|           when (find char separators :test #'char=)
|           when collector
|           collect (coerce collector 'string) into result
|           and do (setq collector nil) end
|           else
|           collect char into collector
|           finally (return (if collector
|                             (append result (list (coerce collector 'string)))
|                             result))))
|

| works as intended with LispWorks, but doesn't work with AllegroCL or
| SBCL.  It seems the latter two simply ignore the (SETQ COLLECTOR NIL)
| form.  My guess is that the code above is somehow incorrect in that it
| modifies a variable which is used in a "collect ... into ..." clause,
| but I couldn't find anything in the CLHS that explicitly says so.


There was a similiar issue posted in the sbcl mailing lists recently ,
and Alastair Bridgewater's explanation is Archived-At:
<http://permalink.gmane.org/gmane.lisp.steel-bank.devel/8668>


CLHS 6.3.1 says 

  During each iteration, the constructs collect and collecting collect
  the value of the supplied form into a list. When iteration
  terminates, the list is returned. The argument var is set to the
  list of collected values; if var is supplied, the loop does not
  return the final list automatically. If var is not supplied, it is
  equivalent to supplying an internal name for var and returning its
  value in a finally clause. The var argument is bound as if by the
  construct with.

From which it is inferred that "the actual list being collected be
held separately from the variable used in the loop body)."

Would that explain it?
--
Madhu
From: Edi Weitz
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <u1wk15tah.fsf@agharta.de>
On Wed, 07 Mar 2007 06:34:28 +0530, Madhu <·······@meer.net> wrote:

> From which it is inferred that "the actual list being collected be
> held separately from the variable used in the loop body)."
>
> Would that explain it?

Er, no.  Thanks for the link, though.  I had read the CLHS entry
myself, but I read it differently.  If something is "bound as if using
a WITH clause," I can modify it within the LOOP's body and have the
new value available in FINALLY clauses.  For what other reason would
they even mention the WITH clause there?

I see no wording that mandates that the list being collected is held
separately from the variable used in the loop body.  I also think
that'd be completely counter-intuitive.

Besides, both SBCL and AllegroCL return 11 (and not 10) from this
loop:

  (loop for i from 1 to 10
        maximize i into k
        do (incf k)
        finally (return k))

To me, the explanation you linked to sounds like an after-the-fact
justification of the implementation.  Unless I see more convincing
arguments.

Thanks,
Edi.

-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")
From: Madhu
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <m3slcho0ud.fsf@robolove.meer.net>
* Edi Weitz <········@agharta.de> <·············@agharta.de> :

| On Wed, 07 Mar 2007 06:34:28 +0530, Madhu <·······@meer.net> wrote:
|
|> From which it is inferred that "the actual list being collected be
|> held separately from the variable used in the loop body)."
|>
|> Would that explain it?
|
| Er, no.  Thanks for the link, though.  I had read the CLHS entry
| myself, but I read it differently.  If something is "bound as if
| using a WITH clause," I can modify it within the LOOP's body and
| have the new value available in FINALLY clauses.  For what other
| reason would they even mention the WITH clause there?

No, as I read it: as it is bound within a (nested) WITH, your
modification would hold only within that single iteration.

On the next iteration the loop variable would be bound with WITH to
the current-collected-list.

| I see no wording that mandates that the list being collected is held
| separately from the variable used in the loop body.  I also think
| that'd be completely counter-intuitive.

Not to me. Let me try again. CLHS 6.3.1 says 

  During each iteration, the constructs collect and collecting collect
  the value of the supplied form into a list. When iteration
  terminates, the list is returned. The argument var is set to the
  list of collected values; if var is supplied, the loop does not
  return the final list automatically. If var is not supplied, it is
  equivalent to supplying an internal name for var and returning its
  value in a finally clause. The var argument is bound as if by the
  construct with.

The behaviour specified that with a COLLECT, ALL values are
collected. There is no provision for mutating that list, and this is
behaviour is identical whether or not a INTO VAR clause is specified.

The only difference is that when a INTO clause is specified, the loop
does not automatically return the final list.

The behaviour is consistent AFAICT



| Besides, both SBCL and AllegroCL return 11 (and not 10) from this
| loop:
|
|   (loop for i from 1 to 10
|         maximize i into k
|         do (incf k)
|         finally (return k))
|

This is consistent. k retains its INCFd value at the end of the last
iteration before execution of the FINAL block.


| To me, the explanation you linked to sounds like an after-the-fact
| justification of the implementation.  Unless I see more convincing
| arguments.

I'm satisfied with things as they are :)

--
Madhu
From: Madhu
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <m3ps7lo0n4.fsf@robolove.meer.net>
* Edi Weitz <········@agharta.de> <·············@agharta.de> :

| On Wed, 07 Mar 2007 06:34:28 +0530, Madhu <·······@meer.net> wrote:
|
|> From which it is inferred that "the actual list being collected be
|> held separately from the variable used in the loop body)."
|>
|> Would that explain it?
|
| Er, no.  Thanks for the link, though.  I had read the CLHS entry
| myself, but I read it differently.  If something is "bound as if
| using a WITH clause," I can modify it within the LOOP's body and
| have the new value available in FINALLY clauses.  For what other
| reason would they even mention the WITH clause there?

No, as I read it: as it is bound within a (nested) WITH, your
modification would hold only within that single iteration.

On the next iteration the loop variable would be bound with WITH to
the current-collected-list.

| I see no wording that mandates that the list being collected is held
| separately from the variable used in the loop body.  I also think
| that'd be completely counter-intuitive.

Not to me. Let me try again. CLHS 6.3.1 says 

  During each iteration, the constructs collect and collecting collect
  the value of the supplied form into a list. When iteration
  terminates, the list is returned. The argument var is set to the
  list of collected values; if var is supplied, the loop does not
  return the final list automatically. If var is not supplied, it is
  equivalent to supplying an internal name for var and returning its
  value in a finally clause. The var argument is bound as if by the
  construct with.

The behaviour specified is that with a COLLECT ALL values are
collected. There is no provision for mutating that list. The behaviour
specified is (should be) identical whether or not a INTO VAR clause is
specified (as there is no reason to believe otherwise)

The only difference is that when a INTO clause is specified, the loop
does not automatically return the final list.

The behaviour is consistent AFAICT



| Besides, both SBCL and AllegroCL return 11 (and not 10) from this
| loop:
|
|   (loop for i from 1 to 10
|         maximize i into k
|         do (incf k)
|         finally (return k))
|

This is consistent. k retains its INCFd value at the end of the last
iteration before execution of the FINAL block.


| To me, the explanation you linked to sounds like an after-the-fact
| justification of the implementation.  Unless I see more convincing
| arguments.


Again,

  If var is not supplied, it is equivalent to supplying an internal
  name for var and returning its value in a finally clause. The var
  argument is bound as if by the construct with.

This binding happens on each iteration of loop. I think it is clear
I'm satisfied with things as they are :)

--
Madhu
From: Edi Weitz
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <ur6s1uzbh.fsf@agharta.de>
On Wed, 07 Mar 2007 15:07:03 +0530, Madhu <·······@meer.net> wrote:

> No, as I read it: as it is bound within a (nested) WITH, your
> modification would hold only within that single iteration.

Except that there is no nested WITH in LOOP...

> | Besides, both SBCL and AllegroCL return 11 (and not 10) from this
> | loop:
> |
> |   (loop for i from 1 to 10
> |         maximize i into k
> |         do (incf k)
> |         finally (return k))
> |
>
> This is consistent. k retains its INCFd value at the end of the last
> iteration before execution of the FINAL block.

So, if that's consistent and K retains its incremented value at the
end of the last iteration and there's a nested WITH somewhere, what
should this return?

  (loop for i from 10 downto 1
        maximize i into k
        do (incf k)
        finally (return k))

Hint: SBCL returns, surprise, 20...

Or this one?

  (loop for i in '(10 1 1 1 11)
        maximize i into k
        do (incf k)
        finally (return k))

Here, SBCL returns 15.

Hmm, consistency...

> This binding happens on each iteration of loop.

That would be the only case in LOOP where a new binding is established
on each iteration, wouldn't it?

> I think it is clear I'm satisfied with things as they are :)

Yes, me too.  I'm satisfied with things as they are in LispWorks... :)

-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")
From: Madhu
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <m3lki9nuat.fsf@robolove.meer.net>
* Edi Weitz <········@agharta.de> <·············@agharta.de> :

| On Wed, 07 Mar 2007 15:07:03 +0530, Madhu <·······@meer.net> wrote:
|
|> No, as I read it: as it is bound within a (nested) WITH, your
|> modification would hold only within that single iteration.
|
| Except that there is no nested WITH in LOOP...

Of course. I was hoping you'd understand what i meant from
context. The mental model I wished to convey was:

(loop for i below 10 collect i into j do (princ j)) ===
(loop for i below 10
      with j = <COLLECTED LIST OF IS>
      do (princ j))


I explained in my earlier followup [in the snipped out part] why I
 think <COLLECTED LIST OF Is> should be modified.


|> | Besides, both SBCL and AllegroCL return 11 (and not 10) from this
|> | loop:
|> |
|> |   (loop for i from 1 to 10
|> |         maximize i into k
|> |         do (incf k)
|> |         finally (return k))
|> |
|>
|> This is consistent. k retains its INCFd value at the end of the last
|> iteration before execution of the FINAL block.

Wait one second. Aren't you are comparing apples to oranges when you
compare COLLECT with MAXIMIZE ?

The spec states (6.1.3)

  The maximize and minimize constructs compare the value of the
  supplied form obtained during the first iteration with values
  obtained in successive iterations. The maximum (for maximize) or
  minimum (for minimize) value encountered is determined (as if by the
  function max for maximize and as if by the function min for
  minimize) and returned.

Now (loop for x below 10 maximize X) will necessarily have to use MAX
on 2 arguments 10 times, storing the value of X.


| So, if that's consistent and K retains its incremented value at the
| end of the last iteration and there's a nested WITH somewhere, what
| should this return?
|
|   (loop for i from 10 downto 1
|         maximize i into k
|         do (incf k)
|         finally (return k))
|

Using the mental model above:

(loop for i below 10
      WITH k = (max k i) ; XXX
      do (incf k)
      finally (return k)


[XXX] if you want to nitpick for an equivalent legal loop construct
`for k = 1 then (max k i)'

| Hint: SBCL returns, surprise, 20...

No surprise there

|
| Or this one?
|
|   (loop for i in '(10 1 1 1 11)
|         maximize i into k
|         do (incf k)
|         finally (return k))
|
| Here, SBCL returns 15.
|
| Hmm, consistency...

Indeed!

|
|> This binding happens on each iteration of loop.
|
| That would be the only case in LOOP where a new binding is established
| on each iteration, wouldn't it?
|
|> I think it is clear I'm satisfied with things as they are :)
|
| Yes, me too.  I'm satisfied with things as they are in LispWorks... :)
OK Then :)
--
Madhu

PS: I think the correct answer to your original question should have been:
Nowhere in the spec is it mentioned that modification of the variable
J is allowed (or disallowed) in the main clauses..

To answer your question why it is mentioned at all consider:

 (loop for name in '(fred sue alice joe june)
       for kids in '((bob ken) () () (kris sunshine) ())
       collect name into foo
       append kids into foo
       finally (return foo))

(The same target can be used multiple times. This is the rationale for
it being mentioned)
From: Edi Weitz
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <uirdduua9.fsf@agharta.de>
On Wed, 07 Mar 2007 17:24:02 +0530, Madhu <·······@meer.net> wrote:

> * Edi Weitz <········@agharta.de> <·············@agharta.de> :
>
> | Except that there is no nested WITH in LOOP...
>
> Of course. I was hoping you'd understand what i meant from
> context. The mental model I wished to convey was:
>
> (loop for i below 10 collect i into j do (princ j)) ===
> (loop for i below 10
>       with j = <COLLECTED LIST OF IS>
>       do (princ j))

Yes, I certainly understood what you meant.  I only doubt that the
authors would have written it that way if they meant what you meant.
They wrote "bound as if by the construct WITH" - they didn't write
"bound as if by an imaginary NESTED-WITH construct."  The construct
WITH is something that's clearly defined, nested WITH is something you
invented.

> Wait one second.  Aren't you are comparing apples to oranges when
> you compare COLLECT with MAXIMIZE ?

That's in the eye of the beholder.  The authors put apples and oranges
into the same section called "value accumulation clauses."  And I was
talking about (counter-)intuitive behaviour.

> Now (loop for x below 10 maximize X) will necessarily have to use
> MAX on 2 arguments 10 times, storing the value of X.

Then use SUM instead of MAXIMIZE.  You can play the same tricks there.

> Using the mental model above:
>
> (loop for i below 10
>       WITH k = (max k i) ; XXX
>       do (incf k)
>       finally (return k)
>
>
> [XXX] if you want to nitpick for an equivalent legal loop construct
> `for k = 1 then (max k i)'
>
> | Hint: SBCL returns, surprise, 20...
>
> No surprise there
>
> |
> | Or this one?
> |
> |   (loop for i in '(10 1 1 1 11)
> |         maximize i into k
> |         do (incf k)
> |         finally (return k))
> |
> | Here, SBCL returns 15.
> |
> | Hmm, consistency...
>
> Indeed!

It seems SBCL doesn't support your mental model, though:

  * (loop for i from 10 downto 1
          for k = 1 then (max k i)
          do (incf k)
          finally (return k))

  18
  * (loop for i from 10 downto 1
          maximize i into k
          do (incf k)
          finally (return k))

  20
  * (loop for i in '(10 1 1 1 11)
          for k = 1 then (max k i)
          do (incf k)
          finally (return k))

  12
  * (loop for i in '(10 1 1 1 11)
          maximize i into k
          do (incf k)
          finally (return k))

  15


-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")
From: Marc Battyani
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <gdudnYaRp8dVLHPYnZ2dnUVZ8vqdnZ2d@giganews.com>
"Edi Weitz" <········@agharta.de> wrote

>(snipped another demonstration that the LOOP spec smells funny ;-)

Here is the ITERATE version for those who don't know what it looks like:

(defun split-string (string &optional (separators " ,-"))
  "Splits STRING into substrings separated by the characters in
 the sequence SEPARATORS.  Empty substrings aren't collected."
  (iterate
   (for char in-vector string)
   (if (find char separators :test #'char=)
       (when collector
         (collect (coerce collector 'string) into result)
         (setq collector nil))
       (collect char into collector))
   (finally (return (if collector
                        (append result (list (coerce collector 'string)))
                        result)))))

CL-USER 324 > (split-string "hello, iterate is way-cool")
("hello" "iterate" "is" "way" "cool")

Cheers,

Marc
From: Chaitanya Gupta
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <esohi1$jli$1@registered.motzarella.org>
Edi Weitz wrote:
> 
> It seems SBCL doesn't support your mental model, though:
> 
>   * (loop for i from 10 downto 1
>           for k = 1 then (max k i)
>           do (incf k)
>           finally (return k))
> 
>   18

That makes sense. I think this should be

CL-USER> (loop for i from 10 downto 1
	    for k = i then (max k i)
	    do (incf k)
	    finally (return k))
20

>   * (loop for i from 10 downto 1
>           maximize i into k
>           do (incf k)
>           finally (return k))
> 
>   20
>   * (loop for i in '(10 1 1 1 11)
>           for k = 1 then (max k i)
>           do (incf k)
>           finally (return k))
> 
>   12

And this should be

CL-USER> (loop for i in '(10 1 1 1 11)
	    for k = i then (max k i)
	    do (incf k)
	    finally (return k))
15


>   * (loop for i in '(10 1 1 1 11)
>           maximize i into k
>           do (incf k)
>           finally (return k))
> 
>   15
> 
> 

It seems to me SBCL does the right thing. (ACL behaves the same way).
But then again, I am no expert. Please correct me if I am wrong.
From: Edi Weitz
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <ur6s0kpmr.fsf@agharta.de>
On Thu, 08 Mar 2007 14:00:03 +0530, Chaitanya Gupta <····@chaitanyagupta.com> wrote:

> Edi Weitz wrote:
>> It seems SBCL doesn't support your mental model, though:
>>   * (loop for i from 10 downto 1
>>           for k = 1 then (max k i)
>>           do (incf k)
>>           finally (return k))
>>   18
>
> That makes sense. I think this should be
>
> CL-USER> (loop for i from 10 downto 1
> 	    for k = i then (max k i)
> 	    do (incf k)
> 	    finally (return k))
> 20

Huh?  So, you're arguing we should change the ANSI standard to make
this 20 instead of 18?

>>   * (loop for i from 10 downto 1
>>           maximize i into k
>>           do (incf k)
>>           finally (return k))
>>   20
>>   * (loop for i in '(10 1 1 1 11)
>>           for k = 1 then (max k i)
>>           do (incf k)
>>           finally (return k))
>>   12
>
> And this should be
>
> CL-USER> (loop for i in '(10 1 1 1 11)
> 	    for k = i then (max k i)
> 	    do (incf k)
> 	    finally (return k))
> 15

And here as well?

>>   * (loop for i in '(10 1 1 1 11)
>>           maximize i into k
>>           do (incf k)
>>           finally (return k))
>>   15
>> 
>
> It seems to me SBCL does the right thing.

I can't follow your line of reasoning, sorry.

-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")
From: Chaitanya Gupta
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <esot5c$vm$1@registered.motzarella.org>
Edi Weitz wrote:
> On Thu, 08 Mar 2007 14:00:03 +0530, Chaitanya Gupta <····@chaitanyagupta.com> wrote:
> 
>> Edi Weitz wrote:
>>> It seems SBCL doesn't support your mental model, though:
>>>   * (loop for i from 10 downto 1
>>>           for k = 1 then (max k i)
>>>           do (incf k)
>>>           finally (return k))
>>>   18
>> That makes sense. I think this should be
>>
>> CL-USER> (loop for i from 10 downto 1
>> 	    for k = i then (max k i)
>> 	    do (incf k)
>> 	    finally (return k))
>> 20
> 
> Huh?  So, you're arguing we should change the ANSI standard to make
> this 20 instead of 18?
> 

No. Here's what the hyperspec (section 6.1.3) says -
"The maximize and minimize constructs compare the value of the supplied 
form obtained during the first iteration with values obtained in 
successive iterations."

So, what makes sense over here:

   for k = 1 then (max k i)
   OR
   for k = i then (max k i)
?

Then again, we see in the same paragraph -
"The argument var accumulates the maximum or minimum value; if var is 
supplied, loop does not return the maximum or minimum automatically. The 
var argument is bound as if by the construct with."

So I think maybe this is closer to what the standard says -

CT-MOBILE> (loop for i from 10 downto 1
	      with k = i
	      do (setf k (max k i))
	      do (incf k)
	      finally (return k))
20
From: Edi Weitz
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <uejo0uepf.fsf@agharta.de>
On Thu, 08 Mar 2007 17:18:09 +0530, Chaitanya Gupta <····@chaitanyagupta.com> wrote:

> So, what makes sense over here:
>
>    for k = 1 then (max k i)
>    OR
>    for k = i then (max k i)
> ?

Ugh, sorry, my bad.  I didn't look closely enough and missed the part
where you exchanged the 1 with the I.

Anyway, for me the case is closed.  It was clear to me I had to change
the function's definition in order to make it work with SBCL and
AllegroCL, I just wanted to know if one of the implementations was
wrong or if the spec is just too vague in this case.  After this
discussion, my opinion is that the spec is simply too vague.

(Yes, Marc, I know what you're going to say now.  I also like ITERATE,
but it's not in the standard and will never be.  It's a changing piece
of open source software without an obligatory specification, and I
wouldn't want to make it a required part of a library just because I
might need it in one or two places.)

Cheers,
Edi.

-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")
From: Marc Battyani
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <Jaednebu_qNYcGzYnZ2dnUVZ8tyqnZ2d@giganews.com>
"Edi Weitz" <········@agharta.de> wrote
>
> Anyway, for me the case is closed.  It was clear to me I had to change
> the function's definition in order to make it work with SBCL and
> AllegroCL, I just wanted to know if one of the implementations was
> wrong or if the spec is just too vague in this case.  After this
> discussion, my opinion is that the spec is simply too vague.
>
> (Yes, Marc, I know what you're going to say now.  I also like ITERATE,
> but it's not in the standard and will never be.  It's a changing piece
> of open source software without an obligatory specification, and I
> wouldn't want to make it a required part of a library just because I
> might need it in one or two places.)

Hum, considering the number of dependencies of Hunchentoot, not all of them 
from yourself, I'm not sure one more would make a difference ;-)

http://www.cl-user.net/asp/libs/Hunchentoot
http://www.cl-user.net/asp/sdataWwcr-sMh9uSXleAq9Fvs-brR$w9TCoxkNQL5R0nR8HBX8yBX8yAvDM==/b/Hunchentoot-dep.pdf

Anyway your point is right and that why I've put a version of ITERATE in the 
cl-pdf repository, so that it stays in sync.

Cheers,

Marc
From: Edi Weitz
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <umz2mugw7.fsf@agharta.de>
On Sat, 10 Mar 2007 00:29:17 +0100, "Marc Battyani" <·············@fractalconcept.com> wrote:

> Hum, considering the number of dependencies of Hunchentoot, not all
> of them from yourself, I'm not sure one more would make a difference
> ;-)

Hehe... :)

> Anyway your point is right and that why I've put a version of
> ITERATE in the cl-pdf repository, so that it stays in sync.

Yes, and now suppose I'll follow your lead and put a version of
ITERATE into the Hunchentoot repository - a slightly different one,
incidentally.  And, say, Kevin Rosenberg puts a third version of
ITERATE into the CLSQL repository.  And then some poor guy has an
application using CL-PDF, Hunchentoot, AND CLSQL.  Which version of
ITERATE will he use?  Which one will ASDF pick?  Hmm...

-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")
From: Pascal Costanza
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <55fd86F24cui9U1@mid.individual.net>
Edi Weitz wrote:
> On Sat, 10 Mar 2007 00:29:17 +0100, "Marc Battyani" <·············@fractalconcept.com> wrote:
> 
>> Hum, considering the number of dependencies of Hunchentoot, not all
>> of them from yourself, I'm not sure one more would make a difference
>> ;-)
> 
> Hehe... :)
> 
>> Anyway your point is right and that why I've put a version of
>> ITERATE in the cl-pdf repository, so that it stays in sync.
> 
> Yes, and now suppose I'll follow your lead and put a version of
> ITERATE into the Hunchentoot repository - a slightly different one,
> incidentally.  And, say, Kevin Rosenberg puts a third version of
> ITERATE into the CLSQL repository.  And then some poor guy has an
> application using CL-PDF, Hunchentoot, AND CLSQL.  Which version of
> ITERATE will he use?  Which one will ASDF pick?  Hmm...

I think the best solution would be if all three versions were used. (!)

However, someone has to come up with a versioning system that works for 
both system definitions and packages at the same time...

Pascal

-- 
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
From: Rob St. Amant
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <esmgnd$k6g$2@blackhelicopter.databasix.com>
Edi Weitz <········@agharta.de> writes:

>   (defun split-string (string &optional (separators " ,-"))
>     "Splits STRING into substrings separated by the characters in the
>   sequence SEPARATORS.  Empty substrings aren't collected."
>     (loop for char across string
>           when (find char separators :test #'char=)
>           when collector
>           collect (coerce collector 'string) into result
>           and do (setq collector nil) end
>           else
>           collect char into collector
>           finally (return (if collector
>                             (append result (list (coerce collector 'string)))
>                             result))))

A bit of a digression: Not being a CL expert, I would have expected
this to break when the "when collector" clause was encountered, but I
see now, from the discussion and reading the spec, why that part of it
should work.  Still, I think that it's unusual and a bit confusing
when the first appearance of a variable in piece of code involves its
being tested.

For what it's worth, spl-string doesn't work in in OpenMCL either.
From: Dmitriy Ivanov
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <eslvao$2k7j$1@news.aha.ru>
Hello Edi,
"Edi Weitz" <········@agharta.de> wrote:

EW> So, this function from Drakma
EW>
EW>   (defun split-string (string &optional (separators " ,-"))
EW>     "Splits STRING into substrings separated by the characters in
EW> thesequence SEPARATORS.  Empty substrings aren't collected."
EW>     (loop for char across string
EW>           when (find char separators :test #'char=)
EW>           when collector
EW>           collect (coerce collector 'string) into result
EW>           and do (setq collector nil) end
EW>           else
EW>           collect char into collector
EW>           finally (return (if collector
EW>                   (append result (list (coerce collector 'string)))
EW>                             result))))
EW>
EW> works as intended with LispWorks, but doesn't work with AllegroCL
EW> or SBCL.  It seems the latter two simply ignore the (SETQ COLLECTOR
EW> NIL) form.  My guess is that the code above is somehow incorrect in
EW> that it modifies a variable which is used in a "collect ... into
EW> ..." clause, but I couldn't find anything in the CLHS that
EW> explicitly says so.
EW>
EW> Any hints?

I remember the discussion on a similar matter in the CL-TYPESETTING mailing
list a couple of years ago. The recommendation was not to mention collector
variables within the finally clause.

One can easily imagine the case when the above loop makes no iteration or
run a single iteration and leads to the situation where the result variable
remains uninitialized at all.
--
Sincerely,
Dmitriy Ivanov
lisp.ystok.ru
From: Edi Weitz
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <ud53lwgvs.fsf@agharta.de>
Hi Dmitriy,

On Wed, 7 Mar 2007 12:04:24 +0300, "Dmitriy Ivanov" <·············@m_aha.ru> wrote:

> One can easily imagine the case when the above loop makes no
> iteration or run a single iteration and leads to the situation where
> the result variable remains uninitialized at all.

Agreed, but that behaviour is specified, at least for similar
situations:

  "If the MAXIMIZE or MINIMIZE clause is never executed, the
   accumulated value is unspecified."

If there is no iteration, I generally don't expect clauses in the body
to be executed... :)

Thanks,
Edi.

-- 

Lisp is not dead, it just smells funny.

Real email: (replace (subseq ·········@agharta.de" 5) "edi")
From: Marc Battyani
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <UL-dnQgUU7OyE3PYnZ2dnUVZ8saonZ2d@giganews.com>
"Dmitriy Ivanov" <·············@m_aha.ru> wrote
> "Edi Weitz" <········@agharta.de> wrote:
>
> EW> So, this function from Drakma
> EW>
> EW>   (defun split-string (string &optional (separators " ,-"))
> EW>     "Splits STRING into substrings separated by the characters in
> EW> thesequence SEPARATORS.  Empty substrings aren't collected."
> EW>     (loop for char across string
> EW>           when (find char separators :test #'char=)
> EW>           when collector
> EW>           collect (coerce collector 'string) into result
> EW>           and do (setq collector nil) end
> EW>           else
> EW>           collect char into collector
> EW>           finally (return (if collector
> EW>                   (append result (list (coerce collector 'string)))
> EW>                             result))))
> EW>
> EW> works as intended with LispWorks, but doesn't work with AllegroCL
> EW> or SBCL.  It seems the latter two simply ignore the (SETQ COLLECTOR
> EW> NIL) form.  My guess is that the code above is somehow incorrect in
> EW> that it modifies a variable which is used in a "collect ... into
> EW> ..." clause, but I couldn't find anything in the CLHS that
> EW> explicitly says so.
> EW>
> EW> Any hints?
>
> I remember the discussion on a similar matter in the CL-TYPESETTING 
> mailing
> list a couple of years ago. The recommendation was not to mention 
> collector
> variables within the finally clause.

Yes, in fact this is precisely why I switched to ITERATE for complex 
iterations in cl-ytpesetting and my other code. The LOOP spec is 
under-specified and ambiguous and is really not of the same quality level 
than the rest of the spec. IIRC I had a problem with the CLISP 
implementation for the same kind of construct but their interpretation of 
the spec, though different from mine and the other implementations, was 
correct too. So I gave up on LOOP for such things. Not to mention that the 
loop syntax turns to ugly write-only code when you try to do this level of 
complexity. ;-)

Also the fact that in LOOP it is illegal to put clause like WHILE before FOR 
ones is a real annoyance IMO. (though it works on most implementations)

Marc
From: Alex Mizrahi
Subject: Re: Illegal LOOP usage?
Date: 
Message-ID: <45eeb106$0$90263$14726298@news.sunsite.dk>
(message (Hello 'Edi)
(you :wrote  :on '(Wed, 07 Mar 2007 01:21:14 +0100))
(

 EW> Any hints?

it's not only weird (i promised myself not to use LOOP in complex 
situations), but it also conses a lot (creating a list and then coercing it 
to string)..
i've recently wrote a piece of code that does approx same thing, but i think 
it conses less and it's not more complex at same time.
(certainly my code is pretty ugly, given here only as an illustration).

(defun split-text-terms (str)
  (loop with beg = nil
    with strlen = (length str)
    with words = nil
    for i from 0 to strlen
    for curchar = (if (< i strlen) (elt str i) #\Space)
    for cur-alpha = (alpha-char-p curchar)
    do (cond
 ((and beg cur-alpha) t) ;;continue word
 (beg (push (subseq str beg i) words)
      (setq beg nil))
 (cur-alpha (setq beg i)))
    finally (return words)))
)
(With-best-regards '(Alex Mizrahi) :aka 'killer_storm)
"?? ???? ??????? ?????")