The major new features of Tcl/Tk 8.6

[This page is being used to work on building more user-oriented descriptions of the new features of 8.6; please have a bit of care as this is work-in-progress right now]

Tcl 8.6 has:

  • object-oriented programming support
  • a stackless evaluation implementation

and much more besides.

Included Object Systems

Tcl 8.6 includes two object systems (that's two more than any previous release of Tcl). But just because we provide them does not mean that your scripts have to change; objects in Tcl are just commands that have a particular way of accessing subcommands that you should already be familiar with:

someCommandThatIsAnObject theSubcommandThatIsAMethod someArgument1 someArgument2

If you've used Tk, you've used that pattern. If you've used many of Tcl's commands (the ensembles, like string or namespace or chan or interp), you've used that pattern.

TclOO (also available as a stand-alone package for Tcl 8.5) is designed to be a small, efficient and powerful object system core that is deeply connected into Tcl and into the way that Tcl works logically. It currently does not ship with a substantial class library, but it is designed to offer a base on which other object systems can be built.

[incr Tcl] 4 also ships with Tcl (and it's an example of an object system built on top of TclOO). There's a much larger class library available for itcl.

Objects are here to help, not to force your code to change.

Getting started with TclOO

# Let's make a class
oo::class create Toaster {
    # The variables in the class; note that this is *not* the [variable] command
    variable toasting afterid

    # Initialization and finalization
    constructor {} {
        set toasting {}
        set afterid {}
    }
    destructor {
        after cancel $afterid
    }

    # A public method; name starts with lower case letter
    method toast {breadProduct} {
        if {$breadProduct eq ""} {
            error "cannot toast nothing!"
        }
        if {$toasting ne ""} {
            error "still toasting $toasting!"
        }
        puts "starting to toast $breadProduct"
        set toasting $breadProduct
        set afterid [after 30000 [namespace code {my DoneToasting}]]
        return
    }

    # A private method (callback handler)
    method DoneToasting {} {
        puts "Nicely burnt $toasting!"
        set toasting {}
    }
}

# Make an instance and use it
set myToaster [Toaster new]
$myToaster toast bagel
vwait forever

More on [incr Tcl]

The Non-Recursive Evaluation Engine

Tcl 8.6 is in many ways a “stackless Tcl”. This has many subtle consequences.

  1. Tcl can now support very deep recursion without heavily loading the C stack. (interp recursionlimit still sets default limit of 1000 levels to catch runaway recursion. Adjust as you like.)
  2. Tcl can now perform tail-calls, which replace the current procedure call with a call to some other command.
  3. Tcl supports coroutines, a system for allowing you to restructure your programs from continuation-passing style into much more straight-line code (with appropriate yields at the points where you want to stop the coroutine from running while something else is happening).

The NRE engine requires no special code from you.
(Well, not unless you want to take advantage of the new features it enables, of course).

What is a coroutine and why do I care?

Coroutines are a fairly old mechanism that fell out of favor for a while. The two principle uses for them are for building generators and for restructuring complex callback-driven code.

As an example, let's write a little event-driven network client:

set s [socket $host $port]
chan event $s readable [list handleLine $s 1]
proc handleLine {s lineNumber} {
    if {[gets $s line] < 0} {
        close $s
    } else {
        puts "${lineNumber}: $line"
        chan event $s readable [list handleLine $s [incr lineNumber]]
    }
}
vwait forever

That's not very nice to read as it's asynchronous and event-driven. Let us use a coroutine to write more clearly what we mean:

set s [socket $host $port]
chan event $s readable [coroutine reader$s apply {s {
    yield [info coroutine]
    try {
        while {[gets $s line] >= 0} {
            puts "[incr lineNumber]: $line"
            yield
        }
    } finally {
        close $s
    }
}} $s]
vwait forever

There's a bit more complexity with setting up the coroutine (which you can hide in a procedure, of course) but the core of the processing is a nice while with a yield in it and a try/finally to clean things up. There's no updating of the callback. What's more, the more complex the interaction, the better the improvement to understandability from being able to say "let's just focus on this bit in a straight-line way".

What's more, you can yield from inside a procedure called from a procedure inside your coroutine implementation. That's what the non-recursive execution engine does for you.

NRE-enabling of extensions

First off, you do not have to alter your C code to make use of NRE. It's only worth considering if you are making calls back into Tcl to evaluate scripts, and even then not in all circumstances. (For example, the Tcl trace command does not use NRE for its internals; that would be far too complicated for words!)

See the example in the documentation of Tcl_NRCreateCommand . In essence, you change from (warning: very abbreviated example):

BlockOfCode1();
result = Tcl_Eval(someScript);
BlockOfCode2();
return result;

Into:

BlockOfCode1();
Tcl_NRAddCallback(runBlockOfCode2);
return Tcl_NREvalObj(someScript);

You handle looping by getting BlockOfCode2 to push itself (with Tcl_NRAddCallback) and run the body script again.

If you compare what we are doing here with what was done in the Tcl coroutine example above, you'll see that the NRE scheme does not actually get rid of the continuation-passing style code: it just moves it into the C level of the API. This is a net win for most people (plus there are the gains from being able to stop putting so many calls on the C stack at any one time).

Error stack with current parameter values

In addition to the errorInfo, which is written when an error occurs, an additional trace, the error stack is generated containing the current parameter values of all called procedures. It is reachable via info errorstack or in the options dict.

Database Support

We now define a standard interface (TDBC) for accessing databases, and ship a few database drivers that support the interface. We also include SQLite, the database engine that started as a Tcl extension and then escaped into the big bad world!

IPv6 Support

Tcl 8.6 is fully IPv6 enabled. As long as your system hostname resolver is configured correctly (sadly, not something that Tcl can do much about if it isn't working right) Tcl's socket will just use the right protocol when connecting to a service.

For server sockets, we now listen on all appropriate protocols. The one observable change is that now when you ask for the local address of a server socket, it can report a list of more than three items. In fact, it will be a list of three items for each protocol supported locally, and you can extract the information easily with foreach:

set s [socket -server someProc 12345]
foreach {address name port} [chan configure $s -sockname] {
    puts "listening on address ${address}, port $port"
}

Note also that hostnames returned when fetching the -sockname and -peername are now usually numeric; support for reverse-DNS is too patchy for the address-to-name translation to be useful in production, unfortunately.

New Commands

lmap and try and zlib and …

The lmap Command

We now provide an lmap command, which is very much like foreach except that its result is a list of the results of the evaluations of the body, rather than the empty string. This means that instead of writing this:

set lengths {}
foreach v $values {
    lappend lengths [string length $v]
}

You can just do this:

set lengths [lmap v $values {string length $v}]

The try Command and Error Codes

The new try command exists to make it easier to trap specific problems, rather than the cruder “catch everything, including unexpected problems” that everyone used to use previously. Thus, you can use:

try {
    set f [open $filename w]
} trap {POSIX EACCES} msg {
    puts "Error opening file for writing\n$msg"
}

Without fear of what would happen if you got to that point and something mundane was wrong (e.g., the filename variable being unset).

You can also use the finally clause to try to make it easier to handle doing cleanup:

set f [open $f]
try {
    doStuff with $f
} finally {
    close $f
}

The error codes that you trap with try are lists that go from least specific to most specific. The mechanism has been around in Tcl for a very long time, but has been under-used in previous releases because of the complexity of working with them. The try command changes that. We've also gone through and defined error codes for a great many internally-generated errors (all of which use TCL as the first word of the error code list). If you find any Tcl-generated errors that have their error code still being NONE (and they're not coming from the error or return commands), that's a reportable bug…

Updated msgcat Package

msgcat::mcflset

Facilates to translate your application using message catalog files by not repeating the locale for each translation.

New Tk Features

PNG Image Support for Tk

Web colors for Tk

Rotated Text on the Canvas

Text on the canvas can now be rotated in an arbitrary direction.

tk busy

Added the ability to lock the application GUI for an entire application or a specific window until released by the program.