I love foreach

Keith Vetter 2003-09-29 -- I love Tcl's foreach command. It's a beautiful command that lets you express elegantly some otherwise cumbersome constructs. Compared to similar constructs in other languages, Tcl's foreach command outshines them all.

People often complain that Tcl's simple syntax can be annoying--it's prolix and confusing to the initiate: when is a comment a comment? (even JO got that one wrong), always having to use expr to do any math, the whole {*} debate (DISCUSSION: Argument Expansion Syntax), etc.

But the foreach command is one of Tcl's strengths.

Most of the common uses of foreach such as walking a list, swapping two variables

   foreach {a b} [list $b $a] break

-or-

   foreach a $b b $a break

, handling multiple return values from a function (see foreach for more details) have similar constructs in other languages, often times more elegantly expressed. - RS: Note that the elegant second form requires a and b to be scalars or empty lists.

But once you get past these simple uses of foreach, other languages can't compare. Take even the simple example of traipsing down a list--in tcl you can take bigger steps. Try doing this in perl (without destroying the data list):

 foreach {x y} $dataSet { ... }

Another simple (but I admit dubious) use of foreach is setting multiple variables at once:

 foreach x [$w canvasx $x] y [$w canvasy $y] break

(FW: wouldn't that rather be

  foreach {x y} [list [$w canvasx $x] [$w canvasy $y]] break

? -- RS: Both have the same effect, but the first "multilist" approach is a bit shorter.)

more detailed foreach can be used to extract list structure and simulate perl or haskell list asigments In Perl

 ($first,$secound,$third) = @list;

In Tcl common

  set first [lindex $list 0]
  set secound [lindex $list 1]
  set third [lindex $list 2]

or with foreach

  foreach {first secound third} $list {}

the problem of this command is that primary invention (assigment) is not obviously. Everyone await some kind of looping. KPV I disagree, it's a Tcl idiom that people come to quickly understand.

But advanced use of foreach that many people don't realize comes when you traipse over a list of variable names. Below is an example from a program I wrote that cleans up user supplied data:

 foreach var {north south east west altitude} {
     set $var [::Data::Fix [set $var]]
 }

Here's an example I just used today:

 foreach col [list red white blue white red] d {4 3 2 1 0} {
    set xy [MakeBox 50 50 [expr {5+$d*8}]]
    .c create oval $xy -fill $col -outline $col
 }

Here's a more complicated example:

 # Get canvas dimensions shrunk by some amount
 foreach who {x0 y0 x1 y1} val [.c cget -scrollregion] d {30 20 -30 -20} {
     set $who [expr {$val + $d}]
 }

Now that I've admitted my love of the foreach command, I confess I have two complaints. First, I'd love some kind of iteration counter. But the obvious way of doing that is via a special variable and that not the Tcl way. Second, I'd love foreach to understand streams, but that's a whole other discussion.

Larry Smith I can see two "Tclish" ways. Since we already have commands that affect the execution of other commands (foreach/break/continue) we could add another to give us the current iteration - call it "loopcount".

  foreach item $itemlist {
    puts "item [loopcount] = $item"
  }

"loopcount" could take an argument to specify what level of looping we want the count of:

  foreach item $itemlist {
    foreach field $item {
      puts "field([loopcount],[loopcount -1]) = $field"
    }
  }

Contrariwise, one could also allow specifying a looping variable:

  foreach item $itemlist -with i {
    puts "item $i = $item"
  }

You would need to use different vars to access the count of an outer loop:

  foreach item $itemlist -with i {
    foreach field $item -with j {
      puts "field($j,$i) = $field"
    }
  }

Could iteration be done with an extra var running over a generated list of ints? -jcw

KPV -- definitely yes, especially if that list of ints were a stream. But the problem is knowing when to stop. The best I could come up with is below, but sometimes $myList is a function return value and then this doesn't work as well:

   foreach var $myList cnt [range 0 [llength $myList]] {...}

RS does integer iteration like this, admittedly not very tricky:

 set i -1
 foreach element $list {
    incr i ;# safer here than in the end, because the loop might be left early w. continue
    # do something with $element and $i here
 }

glennj: or even fall back to the venerable for command

 for {set i 0} {$i < [llength $myList]} {incr i} {
     set element [lindex $myList $i]
     # do something with $element and $i here
 }

KPV: yes, but by using for you lose all the special features of foreach like grabbing multiple elements or traipsing multiple lists.

DKF: The point when you need for is when you need to stride over list elements by variable amounts (e.g. when parsing getargs()-style arguments). At that point, you've no real choice.

jcw - That's a good example of why streams (and coroutines) are more than a gimmick. You can consume items from different parts in the code, and therefore at varying "burn rates". Using some pseudo notation:

    geteach x $argvstream {
      switch -- $x {
        -a { ... }
        -b { set y [get $argvstream]; ... }
      }
    }

Just like "gets" from a channel, really.

EB: Keith, you want something like iterators ? KPV I think so, it seems to me that iterators and streams are different names for the same concept.

Anyway C# has now foreach too. It must be good :). The main advantage of foreach is that input for processing and processing variable is good to see. (That is not by for.) Also many errors in 'for' loops (starting value, end or break condition) can be eleminated.

To have index in the looping Smalltalk collections have extra command 'doWithIndex:' that provide extra index variable

   list doWithIndex: [:each :index |  Transcript show: each printString , ' index ' , index printString; cr].

That can be also simulated in Tcl as new command

   foreachWithIndex e $list {
      puts "$e index $index"
   }

Unfortunately it would be slower as 'foreach' or 'for' that are special byte-coded for speed by interpreter. Here sample implementation of 'foreachWithIndex' in simple (not full foreach) syntax.

 proc foreachWithIndex {var_ref list command} {
    upvar $var_ref var
    upvar index uindex
    set uindex 0
    foreach a $list {
        set var $a
        uplevel $command
        incr uindex
    }
 }

Stu (2005/12/22) Taking foreachWithIndex a step further, traverse will traverse a list, maintain an index count, return the remainder of the list if the entire list was not processed and optionally unset the element var and/or index var.

 proc traverse {var list index body args} {
        upvar 1 $var v
        upvar 1 $index i
        set i -1
        set ii $i
        foreach v $list {
                set i $ii ;# in case the {body} [unset]s 'i'
                incr i    ;# safer here than in the end, because the loop might be left early w. continue (Thanks RS)
                set ii $i
                uplevel 1 $body
        }
        set i $ii ;# in case the {body} [unset]s 'i'
        set ii [incr i]
        foreach a $args {
                if {$a eq {-var}} {
                        unset -nocomplain v ;# '-nocomplain' in case the {body} [unset]s 'v'
                } elseif {$a eq {-index}} {
                        unset i
                }
        }
        return [lrange $list $ii end] ;# (rmax suggested this)
 }


 set lst [list a b c d e]

 puts [traverse elem $lst ind {
        puts "elem:$elem ind:$ind"
        if {$ind > 1} { break }
 } -var -index]

Stu (2006/01/02) A better version.

 proc traverse {var list index body args} {
        set cmd "set $index -1\n"
        append cmd "foreach $var [list $list] \{\n\tincr $index\n$body\n\}\n"
        append cmd "lrange [list $list] \[incr $index\] end"
        foreach a $args {
                if {$a eq {-var}} {
                        append cmd "\[unset -nocomplain $var\]"
                } elseif {$a eq {-index}} {
                        append cmd "\[unset -nocomplain $index\]"
                }
        }
        uplevel 1 $cmd
 }

Lars H: One thought about wanting to "vary the burn rate" -- is there anything which prevents this? I notice (in tclCompile.h) that there are two distinct instructions INST_FOREACH_START4 and INST_FOREACH_STEP4 (although I don't know how one figures out what they do), so what would happen if "foreach step" could appear in the middle of a foreach body? Would that have effects like those of jcw's "get" above?

Assuming the answer is yes, here is some pseudo-code exploiting the idea. It implements a simple drawing language where the commands and their arguments are just elements in a list with no particular structure. I'm using the name next for the step-foreach-command.

  foreach cmd $cmdargseq {
     switch -- $cmd {
        line {
           next {x1 y1 x2 y2}
           $canvas create line $x1 $y1 $x2 $y2
        }
        circle {
           next {x y radius}
           $canvas create oval [expr {$x-$radius}] [expr {$y-$radius}] [expr {$x+$radius}] [expr {$y+$radius}]
        }
     }
  }

A $cmdargseq of "circle 20 20 15 line 18 2 10 10" should then boil down to

  $canvas create oval 5 5 35 35
  $canvas create line 18 2 10 10

Of course, the most important application would probably not be executing code from such mini-languages (although I've had to do write Tcl code for that on some occations, and would have found this next very useful then), but parsing options of Tcl procedures (although that is a kind of mini-language too).

DKF: While in theory you could put INST_FOREACH_STEP in the middle of the loop, you'd need to define what happens if that reaches the end of the list at that point. You'd also need to say why just going round the loop again is not good ehough...

Lars H: The "why not go around the loop again" question has an easy answer: the resulting code gets an unintuitive structure that is hard to understand (just try rewriting the short example above!). I certainly can do (and have done) that, but it's not particularly elegant. As for what the next should do, there are two possibilities: either it sets the extra variables to empty strings or it terminates the loop like a break. In the case of a multi-list foreach the loop should only be terminated if all lists have ended, so there is a certain bias towards the empty string alternative being to more natural one. Right now I'm most inclined to suggest that next should never break, but instead (like scan) return the number of variables that were set to elements from the list. Whether INST_FOREACH_STEP does anything like that is another matter, but its existence suggests that a bytecode command doing this with a foreach loop is possible.

NEM: I'd vote for next throwing an error if there are not enough elements left in the list to fill all the variables asked for. I'm not sure how next would work at all when multiple lists are being iterated over -- how does it know which list to consume from?

Regarding your specific example, you can have fun with code like the following:

 pack [canvas .c] -fill both -expand 1
 proc line {x1 y1 x2 y2 args} {
     .c create line $x1 $y1 $x2 $y2
     return $args
 }
 proc circle {x y r args} {
     .c create oval [expr {$x-$r}] [expr {$y-$r}] [expr {$x+$r}] [expr {$y+$r}]
     return $args
 }
 proc iterate cmds {
     while {[llength $cmds]} { set cmds [uplevel #0 $cmds] }
 }
 iterate [list circle 20 20 15 line 18 2 10 10]

RS feels reminded of Turtle graphics the LOGO way ... - the return $args pattern is very Logo-like :)


slebetman: Talking about "varying burn rate" again, the iterate proc above is close to what I normally use to do it. That is, instead of using the for loop I usually Use while to iterate over a list


Sly: Let me add one more use case. I use foreach to check for existing array keys and do something with it.

 foreach x [array names a ABC] { ; # the key ABC is in here
   ...
 }

FB: Scheme-style lazy and infinite lists are great alternatives to generators and iterators when used with foreach. As part of the Cloverfield project I tried to define a syntax that allows circular references and delayed evaluations (see Cloverfield, section References). For example, the following defines an infinite list of "a" strings, which can be used with foreach (this would obviously loop indefinitely, but you see the point):

{ref root}(a {*}{ref root}{})

Moreover, Cloverfield also provides delayed evaluation. Combined with argument expansion this can also give interesting results. The following defines an infinite list whose elements are a lazily computed sequence of consecutive integers using continuations:

proc getNext {i} {
    return ($i {*}{delay}[getNext [expr {$i+1}]])
}
foreach i [getNext 1] {
    # Iterates over all consecutive integers starting from 1.
}

EKB While not quite the same thing, I wanted to try a pure-Tcl approach to implicitly-defined infinite lists. Here's what I came up with. First, here's the code, a helper proc called "do_implicit" and then the command-creating proc "implicit":

 proc do_implicit {exprs list} {
    upvar $list vals
    array set earray $exprs
    set n [llength $vals]
    if {$n in [array names earray]} {
        set nextval [expr $earray($n)]
    } else {
        set e $earray(default)
        # Add a dummy end to list, then get rid of it
        lappend vals dummy
        regsub -all -- {<-([0-9]+)>} $e {[lindex $vals end-\1]} e
        set nextval [expr $e]
        set vals [lrange $vals 0 end-1]
    }
    lappend vals $nextval
    return $nextval
 }

 proc implicit {name exprs} {
    proc $name list "upvar \$list list2; do_implicit [list $exprs] list2"
 }

This can be used to implicitly define functions that in principle generate infinite lists:

 implicit Fibonacci {
    0 {1}
    1 {2}
    default {<-1> + <-2>}
 }

where <-n> means "use the value calculated n steps prior to this one". (Actually, the Fibonacci series is 0, 1, 1, 2, ... For this example I start with the third element, 1, 2, ... This is just so the example farther down makes some sense.)

Then this can be used the following way:

 % set mylist {}
 % while {[Fibonacci mylist] < 30} {}
 % set mylist
 1 2 3 5 8 13 21 34

Note that it goes one element past the value being tested for (< 30), so if that last value isn't wanted it must be dropped.

So, while this is a bit verbose, here's a way to use foreach:

 % set mylist {}
 % while {[Fibonacci mylist] < 30} {}
 % foreach n [lrange $mylist 0 end-1] {if {$n == 1} {puts "$n day"} else {puts "$n days"}}
 1 day
 2 days
 3 days
 5 days
 8 days
 13 days
 21 days

That way, if you like to use Fibonacci numbers of days for task planning, you have a nice list of days to work with!

And with this proc,

 proc all {name rel val} {
    set list {}
    while "\[\$name list\] $rel \$val" {}
    lrange $list 0 end-1
 }

it can all be done in one line:

 % foreach n [all Fibonacci < 30] {if {$n == 1} {puts "$n day"} else {puts "$n days"}}

EKB Also see Lazy Lists.


Screenshots

I love foreach screenshot2.png

gold added pix, canvas with NEM's iterate oval function above