The serial iterator

the serial incrementor

I stumbled across a sweet way to create an autoincrementing variable for use in loops and such. It allows me to write code like:

% source serial.tcl
% serial create s
% foreach fruit {apples oranges pears banannas} {
>    puts [format {%03d %s} $s $fruit]
>}
001 apples
002 oranges
003 pears
004 banannas

Anyway, I'm so impressed by my own cleverness, that I need to post it online immediately. (It needs SubCommands and named arguments to work...


#serial.tcl
#Copyright April 13, 2005  - Pierre Coueffin

source subcommands.tcl

proc serial {command args} {
   subcommand $command args {
       create {var {start 1} {step 1}} {
           incr start [expr -1 * $step]
           
           upvar $var v

           if {[regexp {read {serial trace (-?[0-9]+)}} \
                    [trace info variable v]]} {
               error "The variable $var has already been declared serial."
           }

           set v $start
           trace add variable v read [list serial trace $step]
       }

       array {var vals {step 1}} {
           upvar $var v
           foreach {idx val} $vals {
               set v($idx) [expr $val - $step]
           }
           trace add variable v read [list serial trace $step]
       }

       trace {step var idx _op} {          
           upvar $var v
           
           if {$idx == {}} {
               incr v $step
           } else {
               incr v($idx) $step
           }
       }

       forget {var} {
           upvar $var v
           trace remove variable v read \
               [list serial trace [serial stepsize v]]
       }

       stepsize {var {newstep {}}} {
           upvar $var v
           if {[llength $newstep] > 0} {
               serial forget v
               serial create v $v $newstep
               return $v
           } else {
               regexp {read {serial trace (-?[0-9]+)}} \
                   [trace info variable v] match step
               return $step
           }
       }

       last {var} {
           upvar $var v
           return [incr v [expr -1 * [serial stepsize v]]]
       }

       default {} {
           error "serial: I don't know how to \"$command\"!"
       }
   }
}

SS With Jim Closures it's as simple as:

proc make-counter {} {lambda {} {{i 0}} {incr i}}
set s [make-counter]
foreach fruit {apples oranges pears banannas} {
    puts [format {%s %s} [$s] $fruit]
}

output:

 1 apples
 2 oranges
 3 pears
 4 banannas

Pierre Coueffin:

Wow, you're fast! I had a few other samples to post, but you managed to slip the edit in while I was checking that the last one worked... I'll just tuck them away here:

% puts $s
5
% puts $s
6
% serial last s
7
% serial last s
7
% serial stepsize s 
1
% serial stepsize s 10
7
% puts $s
17
% set s 5
5
% puts $s
15
% puts $s
25
% puts $s
35

Or how about an array of them?

% serial array sa {0 0 1 5 bob 3} 2
% puts $sa(0)
0
% puts $sa(0)
2
% puts $sa(0)
4
% 
% puts $sa(bob)
3
% puts $sa(bob)
5
% puts $sa(bob)
7

escargo 2005-04-13: This very much like Icon generators, which produce a new value when they are invoked. (Icon generators cannot have their current value and step size increased like these can.)


RS has this variation:

set s 0
trace add variable s read {incr s ;#}
foreach fruit {apples oranges pears bananas} {
    puts [format {%s %s} $s $fruit]
}

which shows

1 apples
2 oranges
3 pears
4 banannas

rdt Wow, leave it to RS to come up with a simple, easy to use solution. Thanks.