Version 4 of foriter - a loop command

Updated 2005-12-14 16:26:45

Sarnold -- 2005/12/13

Typically, when you do some list processing, you can use the foreach command which is efficient, but a construct like:

 for {set i 0} {$i<[llength $mylist]} {incr i} {...}

is slow because it has to call llength at each iteration. I admit it is not new. So a construct like :

 for {set i 0;set len [llength $mylist]} {$i<$len} {incr i} {...}

would be faster, but less readable as there is a new variable that does not mean anything in the rest of the program.

MG Can you not just do something like this:

  for {set i 0} "\$i < [llength $myList]" {incr i} {...}

so that the llength is only evaluated once (at the very beginning), and it's result is used in future (for each loop)?

In Python, you can get more speed by the range() function:

 for i in range(l.length):
     # some code

I submit a new command called foriter that mimics Python's functionality:

 foriter loopvar ?start? end ?increment? body

 loopvar - the name of a variable (existing or not) that holds the counter
 start - defaults to 0
 increment - defaults to 1

Then,

 foriter i start end increment body

is equivalent to:

 for {set i $start} {$i<$end} {incr i $increment} $body

Some benchmarks show that it is faster than for in many cases.


KPV Two comments: first, calls to [llength] are extremely fast, probably just as fast as a variable assignment. Second, I bet that most of the time when a loop goes from 0 to [llength...] there will be, very early in the loop body, a call to [lindex...]. Thus, I believe that the construct that is really needed is a variation on foreach so that it has a counter variable.

Sarnold You are right, a foreach alternative is certainly better. But I still find Python's syntax appealing because it adds sugar.

Well, every programmer has been tought the for syntax early in his/her trainings, but I am *bored* with this syntax because I have to type too much. That is the major reason.


Here is an implementation in C:

 critcl::ccode {
     #include <stdio.h>
 }

 package provide Foriter 0.1

 critcl::ccommand foriter {dummy interp objc objv} {
     int result;
     /* foriter loop : from start to end-1 increment by <increment> do <body> */
     Tcl_Obj *obj_body = NULL;
     /* the int values of the loop range */
     int start = 0;
     int end;
     int increment = 1;
     /* the name of the variable to set the counter */
     char *counter_name = NULL;


     if (objc<4 || objc>6) {
         Tcl_WrongNumArgs(interp, 1, objv, "foriter varname ?start? end ?increment? body");
         return TCL_ERROR;
     }

     if (objc == 4) {
         result = Tcl_GetIntFromObj(interp, objv[2], &end);
         if (result != TCL_OK) {
             return result;
         }
         obj_body = objv[3];
     } else  {
         /*  the user provided 'start' as 2nd argument,
         and the 'end' of the range is then the 3rd argument  */

         result = Tcl_GetIntFromObj(interp, objv[2], &start);
         if (result != TCL_OK) {
             return result;
         }

         result = Tcl_GetIntFromObj(interp, objv[3], &end);
         if (result != TCL_OK) {
             return result;
         }

         if (objc == 5) {
             obj_body = objv[4];
         } else  {
             obj_body = objv[5];
         }
     }

     if (objc == 6) {
         result = Tcl_GetIntFromObj(interp, objv[4], &increment);
         if (result != TCL_OK) {
             return result;
         }
     }

     Tcl_LinkVar(interp, counter_name=Tcl_GetString(objv[1]), &start, TCL_LINK_INT);

     if ((end-start) * increment < 0) {
         /*  the iterating range goes the other way
         than incrementation does                 */
         Tcl_SetObjResult(interp, Tcl_NewStringObj(
         "invalid range : an endless loop would occur", -1));
         return TCL_ERROR;
     }

     /* please note that a negative increment could be used
     (I wonder if one shall do so ?)
     now, the loop begins                                */
     for ( ; (increment > 0)? (start < end): (start > end) ;  ) {
         /* we are into the loop */
         /* printf("."); */
         result = Tcl_EvalObjEx(interp, obj_body, 0);
         if ((result != TCL_OK) && (result != TCL_CONTINUE)) {
             if (result == TCL_ERROR) {
                 Tcl_SetObjResult(interp, Tcl_NewStringObj(
                 "error in foriter body", -1));
                 /*  when an error occurs, we quit the loop
                 and clean up things like References    */
                 break;
             }
             /*  when the user breaks the evaluation
             we have to break out of the loop */
             break;
         }
         /* updating the integer value */
         start += increment;
         Tcl_UpdateLinkedVar(interp, counter_name);

     }
     Tcl_UnlinkVar(interp, counter_name);

     if (result == TCL_ERROR) {
         return result;
     }

     Tcl_ResetResult(interp);
     return TCL_OK;
 }

Category Critcl