Foreach Multiset

A Tcl/Tk Idiom

Problem

How can one efficiently set the value of multiple variables?

Context

Setting multiple variables using a sequence of set statements causes noticeable performance problems in tight loops but the script cannot be reimplemented in C.

Forces

  1. The application must run quickly.
  2. The performance improvements must be implemented at the script level, perhaps to allow code to be downloaded safely or to avoid the slow edit/compile/link/test cycle required when writing C extensions.

Solution

The foreach command can be used to set multiple variables with a single call. If fewer variables than list elements are passed to the command, these variables are changed each time round the loop. However, if the foreach command is invoked with the same number of variables and list elements, and with an empty loop body, the variables are set to the values stored in the list.

Consequences

Multiple variables can be efficiently set with a single command invocation.

The code is obscure if you don't know the idiom. (That's what these pages are for :-)

Known Uses

This idiom was suggested by Ross J.Reedstrom. It is used in code by Jeff Hobbes (check out his tcl pages), and has also been spotted in code by the Sun Tcl Team.

Example Code

The following command sets the variable var1 to the string value1, the variable var2 to the string value2 and the variable var3 to the string value3.

foreach {var1 var2 var3} {value1 value2 value3} {}

This technique can be used to extract elements from a tuple (list) returned from a command, as shown below.

foreach {x y z} [$a_vrml_object getPosition] {}

If the proc might return an empty list, the code above would fail. You can make the code safe, at the expense of an additional iteration, by prepending default values to the list returned by the command:

foreach {x y z} [concat 0 0 0 [$a_vrml_object getPosition]] {}

If the command might return more elements than you need and you only want to process the first few, you can use a break statement as the body of the foreach loop to ensure that the command only performs a single iteration and so doesn't overwrite the variables with the later elements of the list. For example, the code below extracts the red, green and blue components from a tuple specifying a colour; if the operation was extended to return the alpha (transparency) value as the fourth element of the tuple, the code would still work as expected.

foreach {r g b} [$a_vrml_object getColour] break

Obviously, this technique cannot be combined with the use of default values.

Suggested in postings to the comp.lang.tcl newsgroup by Ross J. Reedstrom and Jeff Hobbs.