From: Mark Morschhäuser on
Hello,

after years of using perl I rediscovered tcl. Unfortunately I found a
problem with foreach-loops and want to ask if that is on purpose in tcl (as
google seems not to be my friend in this case)...

If you create a list, then create a foreach loop for this list and if you
append entries to the list within this loop, then the loop ends after the
initial number of list entries are passed and the appended entries are
ignored by the foreach command, though the entries actually were appended to
the list.

Example:

% set l {1 2}
1 2
% llength $l
2
% foreach i $l {
puts $i
if {$i == 2} {
lappend l 3
}
}
1
2
% puts $l
1 2 3
% llength $l
3

Am I doing something wrong? The documentation states "foreach - Iterate over
all elements in one or more lists", but obviously it does not :-)
(To circumvent this, I made a while loop with llength-check now, this works)


Greetings,

Mark
From: Helmut Giese on
Hi Mark,
>Hello,
>
>after years of using perl I rediscovered tcl. Unfortunately I found a
>problem with foreach-loops and want to ask if that is on purpose in tcl (as
>google seems not to be my friend in this case)...
>
>If you create a list, then create a foreach loop for this list and if you
>append entries to the list within this loop, then the loop ends after the
>initial number of list entries are passed and the appended entries are
>ignored by the foreach command, though the entries actually were appended to
>the list.
this is by design: The list is "looked at"/evaluated once at the
beginning. If foreach were to function like you thought it would need
to get passed a _list variable_ - however this would preclude such
nice uses like 'foreach i {1 2 3}'.

>Am I doing something wrong? The documentation states "foreach - Iterate over
>all elements in one or more lists", but obviously it does not :-)
It does. Note that the docs state "list", not "list variable".
You will want to pay attention to this distinction since some list
commands expect a "list variable" (e.g. lappend) while others expect a
list (like lsearch).

>(To circumvent this, I made a while loop with llength-check now, this works)
You mean something like this?
for {set idx 0} {$idx < [llength $l]} {incr idx} {
puts [lindex $l $idx]
if {$idx == 1} {
lappend l 3
}
}
HTH
Helmut Giese
From: Mark Morschhäuser on
Hello,

thanks for the hint "list" vs. "list variable", after thinking about it, I
got the difference there :-)

> You mean something like this?
> for {set idx 0} {$idx < [llength $l]} {incr idx} {
> puts [lindex $l $idx]
> if {$idx == 1} {
> lappend l 3
> }
> }

yes, exactly. This works because llength is evaluated every time again and
keeps track of the actual length of the list, which is what I want in this
context.


Greetings,

Mark
From: Donal K. Fellows on
On 12/06/2010 13:27, Mark Morschhäuser wrote:
> Am I doing something wrong? The documentation states "foreach - Iterate over
> all elements in one or more lists", but obviously it does not :-)

As has been noted, it does because it iterates over the list *value*.
Basically, [foreach] takes a snapshot (efficiently) of the list at the
start of its running and iterates over that. What this means is that it
is always safe to concurrently modify the list in the variables; the
iteration machinery can't get confused. (Compare this with many other
languages where list iteration is fraught with difficulties because of
tangles due to concurrent modification.)

The down-side is that you need to define what you want to happen
manually when you want the iteration to see updates. This is because
while it is fairly easy to guess a strategy that would cope with
appends, it can be very tough in general when elements are modified or
removed and it is better for you to be explicit.

Donal.