Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Illegal LOOP usage?

19 views
Skip to first unread message

Edi Weitz

unread,
Mar 6, 2007, 7:21:14 PM3/6/07
to
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 "spam...@agharta.de" 5) "edi")

Madhu

unread,
Mar 6, 2007, 8:04:28 PM3/6/07
to
* Edi Weitz <uabypo...@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

Edi Weitz

unread,
Mar 7, 2007, 3:53:10 AM3/7/07
to
On Wed, 07 Mar 2007 06:34:28 +0530, Madhu <eno...@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.

Dmitriy Ivanov

unread,
Mar 7, 2007, 4:04:24 AM3/7/07
to
Hello Edi,
"Edi Weitz" <spam...@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


Edi Weitz

unread,
Mar 7, 2007, 4:19:03 AM3/7/07
to
Hi Dmitriy,

On Wed, 7 Mar 2007 12:04:24 +0300, "Dmitriy Ivanov" <divanov_nospa@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... :)

Madhu

unread,
Mar 7, 2007, 4:32:42 AM3/7/07
to

* Edi Weitz <spam...@agharta.de> <u1wk15...@agharta.de> :

| On Wed, 07 Mar 2007 06:34:28 +0530, Madhu <eno...@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

Madhu

unread,
Mar 7, 2007, 4:37:03 AM3/7/07
to

| On Wed, 07 Mar 2007 06:34:28 +0530, Madhu <eno...@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

Marc Battyani

unread,
Mar 7, 2007, 5:04:31 AM3/7/07
to

"Dmitriy Ivanov" <divanov_nospa@m_aha.ru> wrote

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


Edi Weitz

unread,
Mar 7, 2007, 5:23:46 AM3/7/07
to
On Wed, 07 Mar 2007 15:07:03 +0530, Madhu <eno...@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... :)

Madhu

unread,
Mar 7, 2007, 6:54:02 AM3/7/07
to
* Edi Weitz <spam...@agharta.de> <ur6s1u...@agharta.de> :

| On Wed, 07 Mar 2007 15:07:03 +0530, Madhu <eno...@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)

Edi Weitz

unread,
Mar 7, 2007, 7:12:30 AM3/7/07
to
On Wed, 07 Mar 2007 17:24:02 +0530, Madhu <eno...@meer.net> wrote:

> | 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

Alex Mizrahi

unread,
Mar 7, 2007, 7:33:05 AM3/7/07
to
(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)
"?? ???? ??????? ?????")


Marc Battyani

unread,
Mar 7, 2007, 7:36:25 AM3/7/07
to
"Edi Weitz" <spam...@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


Tim Bradshaw

unread,
Mar 7, 2007, 7:59:20 AM3/7/07
to
On Mar 7, 12:21 am, Edi Weitz <spamt...@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

Marc Battyani

unread,
Mar 7, 2007, 8:29:11 AM3/7/07
to

"Tim Bradshaw" <tfb+g...@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


Edi Weitz

unread,
Mar 7, 2007, 8:30:09 AM3/7/07
to
On 7 Mar 2007 04:59:20 -0800, "Tim Bradshaw" <tfb+g...@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.

Edi Weitz

unread,
Mar 7, 2007, 8:31:53 AM3/7/07
to
On Wed, 7 Mar 2007 14:29:11 +0100, "Marc Battyani" <Marc.B...@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.

Rob St. Amant

unread,
Mar 7, 2007, 9:03:25 AM3/7/07
to
Edi Weitz <spam...@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.

Tim Bradshaw

unread,
Mar 7, 2007, 9:54:56 AM3/7/07
to
On Mar 7, 1:30 pm, Edi Weitz <spamt...@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.

Edi Weitz

unread,
Mar 7, 2007, 10:10:49 AM3/7/07
to
On 7 Mar 2007 06:54:56 -0800, "Tim Bradshaw" <tfb+g...@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... :)

Ken Tilton

unread,
Mar 7, 2007, 2:19:08 PM3/7/07
to

Tim Bradshaw wrote:
> On Mar 7, 1:30 pm, Edi Weitz <spamt...@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

Chaitanya Gupta

unread,
Mar 8, 2007, 3:30:03 AM3/8/07
to
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.

Edi Weitz

unread,
Mar 8, 2007, 5:15:24 AM3/8/07
to
On Thu, 08 Mar 2007 14:00:03 +0530, Chaitanya Gupta <ma...@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.

Chaitanya Gupta

unread,
Mar 8, 2007, 6:48:09 AM3/8/07
to
Edi Weitz wrote:
> On Thu, 08 Mar 2007 14:00:03 +0530, Chaitanya Gupta <ma...@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))

Edi Weitz

unread,
Mar 8, 2007, 7:01:16 AM3/8/07
to
On Thu, 08 Mar 2007 17:18:09 +0530, Chaitanya Gupta <ma...@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.

tbur...@gmail.com

unread,
Mar 8, 2007, 7:48:42 AM3/8/07
to
On Mar 7, 1:59 pm, "Tim Bradshaw" <tfb+goo...@tfeb.org> wrote:
> On Mar 7, 12:21 am, Edi Weitz <spamt...@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.

Tim Bradshaw

unread,
Mar 8, 2007, 8:47:50 AM3/8/07
to
On Mar 7, 7:19 pm, Ken Tilton <kentil...@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

Edi Weitz

unread,
Mar 8, 2007, 9:02:16 AM3/8/07
to
On 8 Mar 2007 04:48:42 -0800, tbur...@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)))))))))

Tim Bradshaw

unread,
Mar 8, 2007, 10:27:21 AM3/8/07
to
On Mar 8, 12:48 pm, tburd...@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


Marc Battyani

unread,
Mar 9, 2007, 6:29:17 PM3/9/07
to

"Edi Weitz" <spam...@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


Edi Weitz

unread,
Mar 9, 2007, 6:38:32 PM3/9/07
to
On Sat, 10 Mar 2007 00:29:17 +0100, "Marc Battyani" <Marc.B...@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...

Pascal Costanza

unread,
Mar 10, 2007, 4:48:53 AM3/10/07
to
Edi Weitz wrote:
> On Sat, 10 Mar 2007 00:29:17 +0100, "Marc Battyani" <Marc.B...@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/

WJ

unread,
May 30, 2011, 4:24:35 PM5/30/11
to
Edi Weitz wrote:

> 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.

Arc:

(def split-string (str . separators)
(withs (separators (or (car separators) " ,-")
test (fn (c) (pos c separators))
p1 0
p2)
(accum save
(while p1
(assign p2 (pos test str p1))
(if (isnt p1 p2) (save (cut str p1 p2)))
(assign p1 (and p2 (pos (complement test) str p2)))))))

Marco Antoniotti

unread,
May 31, 2011, 4:34:07 AM5/31/11
to

A while LOOP in Arc?

Yawn

MA

0 new messages