Upgrading from Tcl 7.6

The core of the Tcl interpreter has been replaced with an on-the-fly compiler than translated Tcl scripts to bytecodes, resulting in faster running of code. In Tcl 8.X, strings are replaced with Tcl_Obj structures ("objects"). These can hold both a string value and an internal form such as a binary integer or compiled bytecodes. The new objects make it possible to store information in efficient internal forms. This avoids the constant translations to and from strings that happened with the old interpreter. Sometimes, particularly errors, this can cause scripts to behave differently than in previous versions of Tcl.

Tcl 8.X causes a few changes to Tcl/Tk scripts or C extensions. Most of these changes let Tcl programs run faster.

These changes are:

  • Compilation errors return errors immediately, before running a script.

    Compilation errors are returned for scripts with malformed expression words. For example, words with missing close braces or quotes. In addition, several commands are treated specially by the Tcl bytecode compiler and are compiled into an in-line sequence of instructions. This is for better running speed.

    In Tcl 8.X, these commands are:

    • break
    • catch
    • continue
    • expr
    • for
    • foreach
    • if
    • incr
    • set
    • while

    If a script includes one of these commands with the incorrect number of argument words, then a compilation error is returned immediately. At this point, the script is not run. For example, set a xxx yyy. By returning a compilation error immediately, you can find typos and other syntax errors in your scripts as soon as possible.

  • catch no longer catches compilation errors.

    Because compilation errors in scripts are returned immediately, catch no longer prevents compilation errors from stopping command interpretation.

    For example, catch {set} immediately reports the error wrong # args: should be "set varName ?newValue?".

    To have catch handle compile time errors, give a script to it that is only determined at runtime. For example catch [concat {set}].

  • Scripts are completely compiled.

    Tcl 7.6 would ignore any characters in a script after the last command run. This would let you, for example, put comments after the exit command that terminated a script without having to start each comment line with a #.

    For example, previously you would write:

    initialize ;# the commands
    compute ;# of the
    finalize ;# program
    exit ;# stop execution
    This is the first line in the script’s change log.
    A second line in the change log.

    Tcl 8.X attempts to compile those lines. You should put a # at the start of each comment line.

  • Lists are aggressively parsed.

    Lists are a real, and significantly faster, data type and not strings that are rescanned each time. They are used the same way, but strings are converted to a faster internal representation behind the scenes of the first list operation. Lists are now completely parsed.

    Tcl 8.X reports an error when you have a malformed list, and the erroneous part was after the point an element was inserted or extracted.

    For example, in Tcl 8.X:

    lindex {a b "c" d e} 1

    This returns the error list element in quotes followed by "d" instead of space.

    In Tcl 7.6 it returns b.

  • List operations do not preserve the exact white space between elements.

    List operations in Tcl 7.6 always retained the white space between list elements. In Tcl 8.X, list operations return lists whose elements are separated by a single space.

    In this example, the first command sets the variable x to a string containing two tab characters:

    set x {one two three} lrange $x 0 1

    In Tcl 8.X, the lrange command returns "one two." Programs that preserve white space should use string operations or use a combination of the split and join commands.

    For example:

    set x {one two three}
    join [lrange [split $x{ }]0 1] { }

    This preserves the tab between the list elements. There is a tab character inside each pair of braces.

  • Fewer floating-to-string conversions, and the associated rounding, can change program behavior.

    Both Tcl 8.X and 7.6 use full IEEE floating-point precision, about 17 decimal digits, when computing expressions. They both round floating-point values when converting them to strings. By default, Tcl 8.X retains 12 digits and Tcl 7.6 keeps 6 digits. The new object system in Tcl 8.X causes fewer floating-to-string conversions, and the associated rounding, to happen than in Tcl 7.6. These changes mean that floating-point computation is more accurate and faster in Tcl 8.X, but there are sometimes behavioral changes.

    For example:

    for{set x 0.0}{$x != 4.0}{set x [expr $x+0.1]}{puts $x}

    This terminates in Tcl 7.6 but loops forever in Tcl 8.X. This is because the fraction 0.1 cannot be exactly represented as an IEEE floating-point value. Repeatedly adding it to 0.0 never produces a floating-point value that is exactly 4.0. This loop terminated in Tcl 7.6 only because of the rounding that was performed at every assignment to the variable x.

    The solution in Tcl 8.X is to use approximate comparisons for floating-point. When looping until the value of a floating-point expression reaches a target value, test whether the expression is "close enough" to the target.

    For example:

    for{set x 0.0}{abs($x-4.0)>0.001}{set x[expr $x+0.1]}{puts $x}

    This stops when the variable x reaches 4.0.

  • The original strings in expressions are retained.

    For example:

    expr{"0y"<"0x3"}

    This yields 0, not 1, in Tcl 8.X because the original string for 0.3 is not lost. Tcl always tries to convert expression operands to numbers when possible. In Tcl 7.6, the string 0x3 was lost when being converted to a number (3) by the expression implementation and then back into a string. At this point a string comparison had to be performed. This meant Tcl 7.6 did a string comparison between 0y and 3. Because Tcl values are now stored in Tcl objects that hold an internal form and a string, the original string is not lost. Tcl 8.X can do the string comparison between 0y and 0x3.

  • info cmdcount is no longer accurate.

    Compiled commands are compiled away and their running is no longer counted. In Tcl 8.X, this includes the commands:

    • break
    • catch
    • continue
    • expr
    • for
    • foreach
    • if
    • incr
    • set
    • while
  • append no longer triggers read traces.

    The append command no longer triggers read traces when retrieving the old values of variables before doing the append operation. It only triggers a write trace after each append.

    The lappend command still triggers read traces in this situation. lappend has changed to behave as append.

  • lappend triggers only one write trace after appending all arguments to the list.
  • Error tracebacks are shorter.

    There are fewer recursive calls to Tcl_Eval because some commands such as while and if have been compiled into a sequence of bytecoded instructions. Tracebacks often have fewer traceback levels than in the past.

  • The length of local variable names does not matter.

    For most commands, the compiler looks up local variables in a procedure at compile time. Then, it emits instructions that, at run time, take the same amount of time regardless of the length of the variable names.