foreach , a built-in Tcl command, iterates over all elements in one or more lists and evaluates a script at each iteration
foreach implements a loop where the loop variable(s) take on values from one or more lists. In the simplest case, there is one loop variable, varname, and one list, listn, and with each iteration, the next item in list is assigned to varname. body is a Tcl script that is evaluated once for each iteration of the loop.
Although any of the lists passed as arguments can be modified by body, the changes will not be seen by foreach as the loop iterations continue. Internally, Tcl will make a copy of the list where necessary in order to preserve this behaviour. If a list is large, modifying it within body can therefore have an adverse impact on memory.
To take multiple items from the list at each iteration, supply multiple variable names in varlist:
foreach {name gender} [list Bob male Joe male Sarah female Kim unknown] { ... }
Multiple lists can be traversed in simultaneously by adding them as additional arguments to the command. During each iteration of the loop, subsequent items from each list are assigned to the corresponding variables. Items in each list are used in order from first to last, and each item is used exactly once.
$ tclsh % foreach a [list 1 2 3 4] b [list 5 6 7 8] c [list a b c d] d [list w x y z] { puts "$a $b $c $d" } 1 5 a w 2 6 b x 3 7 c y 4 8 d z %
Combining multiple variable names and multiple lists:
foreach {fname lname} [join $first_and_last_names] phone $phone_numbers { # code block }
The total number of loop iterations is large enough to use up all the values from all the lists. If a list does not contain enough items for each of its loop variables in each iteration, empty values are used for the missing elements:
% unset -nocomplain a b c % foreach {a b c} {1 2} {break} % info exists c 1
break and continue may be invoked inside body, and have the same effect as in for. foreach returns an empty string. (From: TclHelp)
If all the list arguments are empty lists, no assignment to the corresponding variables occurs, and body is never evaluated:
% unset -nocomplain a b c % foreach {a b c} {} {break;} % info exists a 0
The documentation could be interpreted as implying that the corresponding variables would receive empty values:
but then it goes on to dispel that interpretation, since zero iterations is large enough to iterate over zero items:
set l [list a bc def 1 23 456] set m 0 foreach i $l { incr m puts "member $m is $i" }
output:
member 1 is a member 2 is bc member 3 is def member 4 is 1 member 5 is 23 member 6 is 456
To stride through a list, picking up every n'th item, a dummy variable like $- can be used:
foreach {- i} $list { do something with $i while ignoring $- }
To save an array to a file:
foreach {key value} [array get myArray] { puts $myFile [list set myArray($key) $value] }
Of course, you could have written:
puts $myFile [list array set myArray [array get myArray]]
but when editing such a file, the lack of line endings might give your text editor indigestion.
To save an array to a file, ordering it alphabetically by key:
foreach {key} [lsort -dictionary [array names myArray]] { puts $myFile [list set myArray($key) $myArray($key)] }
In a canvas, applying foreach to the result of the bbox is often a convenient way to create canvas layouts. The following script illustrates the idea.
grid [canvas .c -width 400 -height 300 -bg gray85] set i [.c create text 200 50 -text "One" -anchor n] foreach item { Two Three Four } { foreach { - - - y } [.c bbox $i] break set nexty [expr { $y + 50 }] .c create line 200 $y 200 $nexty -arrow last set i [.c create text 200 $nexty -text $item -anchor n] }
KPV: A similar example illustrates finding the dimensions of a canvas to shrink the size to provide a margin around the layout:
# Get canvas dimensions shrunk by some foreach who {x0 y0 x1 y1} val [.c cget -scrollregion] d {30 30 -30 -30} { set $who [expr {$val + $d}] }
One popular idiom is to use the varlist assignments to split up a list. The body in such cases would just be break to exit foreach on the first iteration:
foreach {first second third} $list break ;# RS
A similar pattern can be used to swap variable values:
foreach {a b} [list $b $a] break
It might be tempting to abbreviate the above to foreach a $b b $a break, but don't do that; it really sets b to the result of [lindex $a 0] and vice versa, which in general is something quite different, and even when it isn't it will result in shimmering.
AMG: lassign can do both these tasks.
lassign $list first second third lassign [list $b $a] a b
Unlike foreach, it returns any unused elements from its first argument. This makes it useful for detecting extra arguments or popping elements off a stack. Also unlike foreach, it won't create variables having an empty string value when its first argument doesn't have enough elements. This makes it useful for detecting insufficient arguments.
When using incr in combination with foreach to get the index of the item in the list, it's a good idea to set the initial value to -1 instead of 0, and incr at the beginning of the script rather than at the end, so that continue can be placed anywhere in the script:
set index -1 foreach item $list { incr index # Now do something using both $index and $item ... if {something} continue ... }
The reason to incr index first in the loop body is that this makes continue do the right thing.
foreach can be used as a code block that runs exactly once, but can be exited from with break or continue (seen in a c.l.t post by Bruce Hartweg):
foreach _ _ { # some processing if {$somecondition} break # some other processing if {$someothercondition} break # still more processing }
This is an alternative to nested if structures.
RS is not sure whether to recommend this style.. but proof again that you can do more with Tcl than you'd imagine...;-) Well hell, then add some sugar:
interp alias {} breakable {} foreach _ _ breakable { ;# or 'fragile'? # some processing if {$somecondition} break # some other processing if {$someothercondition} break # still more processing }
Or, to make your intention clearer:
interp alias {} make {} foreach make or break { ... }
AMG: This is like the do {...} while (0); idiom in C, which is useful not only in macros but also for creating a "structured goto" using break. Perhaps unfortunately, continue can't be used to go back to the top, since it first checks the loop condition; otherwise this idiom would effectively create two line labels at the same time. ;^) Very occasionally I wish for a "redo" statement that restarts the current iteration of the loop, bypassing both the loop condition and for's increment clause.
SS wonders why foreach doesn't return the result of the last executed command. It can be useful for programmers using Tcl in a functional style. The same also apply to for. CL likes the idea.
rmax: Another useful return value (maybe even closer to functional programming) could be the unprocessed part of the list if a break occured in the loop body.
I sometimes find myself doing stuff like this:
foreach {a b c} $args break set args [lrange $args 3 end]
This could be done easier and would be less error prone if foreach returned the tail of the list:
set args [foreach {a b c} $args break]
Of course, this feature can only work in a useful way if foreach was called with just one list.
RS: This is what lassign (from 8.5, or TclX) does:
set args [lassign $args a b c]
SS: Yep rmax, my past idea evolved almost exactly into a new idea similar to your suggestion. Actually I'm not sure about many different options:
set args [foreach {a b c} $args]
does what your example do.
set args [foreach {a b c} $args]
is still valid.
AMG: [lmap] works just like [foreach] but indeed does have a return value. The returned value is a list accumulating the results of each iteration.