Updated 2014-04-16 21:28:28 by AMG

incr increments a variable

Synopsis  edit

incr variable ?increment?

Documentation  edit

official reference

Description  edit

Before Tcl 8.5, the variable had to exist before the incr call. variable must have value that can be interpreted as a an integer. increment must also be an integer and defaults to 1 if not specified.
incr i

If $i didn't previously exist, 1 will now be its value.

If $i is a non-integral real number, [incr] could not be used, but [set] could:
set i [expr {$i + 1}]

increment can be a negative number, making it possible to increment downwards.

RS: increment may be zero. This sounds useless, but has the side effect that the variable is type-checked to be integer, which avoids endless loops (the infinity trick) like
for {set i 0} {$i < $max} {incr i}  {...}

when max is a non-numeric string whose first character sorts higher than "9". If max comes in as user-supplied parameter, this may well happen... A simple incr max 0 safeguards that it must be an integer.

Post-increment  edit

RS 2013-12-07 - In C, integer variables can be pre-incremented (++i: first do increment, then retrieve value, like incr does), or post-incremented (i++: first retrieve value, then do increment). Here is how to do the latter in Tcl:
proc pincr {_var {amount 1}} {
    upvar 1 $_var var
    if ![info exists var] {set var 0}
    set  res $var
    incr var $amount
    return $res
# -------------- and now testing:
% set x 42
% foreach i {a b c} {puts [pincr x]}

AMG: This can also be done with [K].
proc K {x y} {set x}
proc pincr {varName {amount 1}} {
    upvar 1 $varName var
    K $var [incr var]
% set x 42; lmap i {a b c} {pincr x}
42 43 44
% set x 42; lmap i {a b c} {K $x [incr x]}
42 43 44

Here's another way of going about it:
% set x 42
% lindex $x[incr x; list]
% set x

The single-argument [lindex] is just there to return its one argument. Other commands can fulfill the same role.

Here's the 8.6.1 disassembly for the above:
 tcl::unsupported::disassemble lambda {{x} {lindex $x[incr x; list]}}
ByteCode 0x000000000253A9B0, refCt 1, epoch 16, interp 0x00000000025765B0 (epoch 16)
  Source "lindex $x[incr x; list]"
  Cmds 3, src 23, inst 34, litObjs 1, aux 0, stkDepth 2, code/src 0.00
  Proc 0x0000000002533810, refCt 1, args 1, compiled locals 1
      slot 0, scalar, arg, "x"
  Commands 3:
      1: pc 0-32, src 0-22        2: pc 2-14, src 10-15
      3: pc 15-25, src 18-21
  Command 1: "lindex $x[incr x; list]"
    (0) loadScalar1 %v0         # var "x"
  Command 2: "incr x"
    (2) startCommand +12 1         # next cmd at pc 14
    (11) incrScalar1Imm %v0 +1         # var "x"
    (14) pop 
  Command 3: "list"
    (15) startCommand +11 1         # next cmd at pc 26
    (24) push1 0         # ""
    (26) concat1 2 
    (28) lindexMulti 1 
    (33) done

I see things can be optimized further by making the bytecode generation engine aware that the sequence "push1 0; concat1 2" is a no-op when literal #0 is empty string. Also it can be taught that "lindexMulti 1" basically doesn't do anything either.

Incrementing Doubles  edit

Incrementing doubles as well as ints can be done like this:
proc += {varName {amount 1}} {
    upvar 1 $varName var
    set var [expr {$var+$amount}]
} ;# RS

AMG: Why doesn't [incr] do this natively?

Incrementing Hexadecimal  edit

Csan: An enhanced incr proc which adds hexadecimal string incrementing (delta could be e.g. 0xF):
proc incr {varName {delta 1}} { 
    upvar 1 $varName var
    if {[regexp -- ^0x.*$ $var]} {     
        set var 0x[format %X [expr [scan $var %x]+$delta]]   
    } {
        if {[string equal [format %d [scan $var %d]] $var]} {
            set var [expr {$var+$delta}]

Trying it out:
% set n 1 ; incr n
% set n 0xA ; incr n
% set n 0xFE ; incr n
% set n 0xFF ; incr n
% set n 0xF0 ; incr n 0xE
% set n 123H ; incr n

My thanks go to Lars H for pointing out the weakness of the previous code ;) (do you know of a better way to tell if a number is a decimal besides checking if a scan-format produces the same string?)
% set n 0xF
% incr n 
% incr n 0xA

Lars H: Only numbers which cannot be parsed as decimal integers are incremented as hexadecimals, but a lot of hexadecimals can be parsed as decimals (with a completely different value). The result is that what starts out hexadecimal probably doesn't stay so for very long.

A Non-Incrementing [incr]  edit

Sarnold: String and list indices are difficult to handle with additions and substractions. Replace the following code which mimics [split]:
while {[set idx [string first ( $str]] >= 0} {
    lappend picked [string range $str 0 [expr {$idx-1}]]
    set str [string range $str [expr {$idx+1}]]

while {[set idx [string first ( $str]] >= 0} {
    lappend picked [string range $str 0 [incr idx -1]]
    set str [string range $str [incr idx 2]]

But this is not as readable and error-prone. Here the problem is that line 2
...[incr idx -1]

modifies idx, then we have to increment later by 2 instead of 1. Lots of confusion...

So we need a [noincr] proc that returns the result of the increment, without modifying the variable.
while {[set idx [string first ( $str]]>=0} {
    lappend picked [string range $str 0 [noincr idx -1]]
    set str [string range $str [noincr idx]]

It is then more readable, and of course more maintainable.

Here is [noincr]:
proc noincr {var {i 1}} {
    upvar $var v
    set x $v
    incr x $i

PYK 2013-10-15: I would just stick with the [expr] variant.

Not Sure What This is Good For - Delete This Section?  edit

avi: This might be an alternate implementation in the lines of C/C++ style ++ ops
proc incrWrap {pChar} {
    upvar 1 $pChar lVar
    if { ![info exists lVar] || [string is double -strict $lVar] } {
        incr lVar
    } else {
        scan $lVar %c lVal
        incr lVal
        set lVar [format %c $lVal]

AMG: I fixed a few bugs (inability to handle variables that don't exist yet, using the variable name instead of its value), but I still don't understand what this code is useful for. It does ordinary incr on strings which appear to be numbers (even though [incr] doesn't support floating-point numbers, only integers), and it increments the ASCII/Unicode value of the first character of anything else (beware empty strings and strings longer than one character!). But since it's really only guessing whether a string is intended to be a number or a character, it can guess wrong:
set x .        ;# returns .
incrWrap x     ;# returns /
incrWrap x     ;# returns 0
incrWrap x     ;# returns 1
# ... keep going ..].
incrWrap x     ;# returns 8
incrWrap x     ;# returns 9
incrWrap x     ;# returns 10 even though I wanted :

Here's my recommendation. Use [incr] directly when you need to increment and decrement integers, and use [scan] and [format] when you need to convert between characters and numeric character codes. Don't create a wrapper for something until you find a demonstrable need, or else you will make your code more confusing, not less confusing.

avi: incrWrap (with the amendments you made) can be used as a drop-in substitute for incr in a Tcl for loop which needs to handle both character and ints. As far as "need" is concerned, it is the same as why a procedure is written instead of a bunch of lines of code.

Historical: Platform-Specific Caveats  edit

In versions of Tcl prior to 8.5, [incr] was limited by the size of int in the on the platform Tcl was compiled for. This section only applies to those historical versions of Tcl.

Twylite 2005-01-04: In 8.4, [incr] works according to the underlying type of the integer, which is determined by its size. so:
% set i [expr 2147483647]
% incr i
% set i [expr 21474836470]
% incr i

A number larger than 32 bits can be formed as a string. To use 32-bit numbers in a calculation that will have a 64-bit result you can use the wide type (see expr problems with int)

The only solution I am aware of for a "64-bit incr" is:
set i [expr wide($i) + 1]

Historical: Tolerant Incrementer  edit

This section is historical, since modern [incr] automatically creates the variable, if necessary.

This tolerant incrementer creates the variable if necessary, and initializes it with 0:
proc inc {varName {amount 1}} {
    upvar 1 $varName var
    if {![info exists var]} {set var 0}
    incr var $amount

KPV: You can avoid [info exists] altogether by doing:
set var [expr [append var + $amount]]

Unfortunately, while this is tighter, it is slower due to shimmering.'

Lars H: The need to parse the expression generated probably also contributes to that being slower. Another idiom is
if {[catch {incr var $amount}]} then {set var $amount}

If the variable exists most of the time then this is faster than the [info exists] approach.

Half-Bakery: Using [incr] with Strings  edit

RS had a half-baked idea these days - extend incr to also work on characters (like in C), so we could write:
for {set letter "A"} {$letter<="Z"} {incr letter} {...}

rmax pointed rightly out that ambiguities come up as soon as the character is a digit: set i 9; incr i would make it 10, not ":" as expected - so maybe with a -char switch which still looks better than
set letter [format %c [expr {[scan $letter %c]+1}]]

Is there anything peculiar that happens with this in regards to encoding? Will the incrementing be of the utf8 byte value? Are there multi-byte character issues with regards to incrementing? What happens at wrap around time if only 8 bits are being used? Wrap to 0? Is this a 7 or 8 bit value?

RS: To Tcl (since 8.1), all characters are Unicodes and potentially 16 bits wide (this may have to change to 32), which mostly are stored as UTF characters, 1..3 bytes wide. I would propose that incr -char reacts like the format..expr..scan code above, just faster, and might throw an error if crossing 0xFFFF. Incrementing strings of more than a char wide seems of little use to me, could raise an error as well.

  1. Are all 16 bit bytes valid unicode characters? (No. Some codes (0xFFFE, 0xFEFF, others?) are explicitly not characters, and others are non-spacing diacriticals that shouldn't be used on their own. -SCT)
  2. When I was talking about multi-byte characters, I was referring to the 2 and 3 byte long sequences for a unicode character. However, now that you mention it, I would like to be able to say
set name "/tmp/filename0"
incr name

and have the file name go from filename0 to filename9, then filenamea-z, etc.

RS: Hm. A long way from "9" to "a" in ASCII or Unicode.. How about using the self-incrementing proc
proc intgen {{seed 0}} {
    set self [lindex [info level 0] 0]
    proc $self "{seed [incr seed]}" [info body $self]
    set seed
} ;# RS

from Braintwisters, and then write (where after filename9 will come filename10):
set name "/tmp/filename[intgen]"

Here is a corresponding decrement function:
proc decr { int { n 1 } } {
    if { [ catch {
        uplevel incr $int -$n
    } err ] } {
        return -code error "decr: $err"
    return [ uplevel set $int ]

RS wonders what the advantages against
proc decr {var {n 1}} {
    uplevel 1 [list incr $var -$n]

are (or against just writing incr i -1)?

Martin Lemburg: I love readable code and using `decr var 1 is more readable than incr var -1', or am I wrong?

Isn't it worth to discuss readability of code?

DKF: Good bonus to readability, not so good for speed (the incr version will be bytecoded much more efficiently.) Tricky trade-off that...