Emulating closures in Tcl

Some languages (like Lisp family, Smalltalk, Lua) contain an interesting feature called closures. A Closure represents a block of code with surrounding context, whose execution is deferred. A block can be executed later (probably more then once) with old context and newly passed arguments.

Closures often used to provide customized behavior for some algorithm.

Tcl has no direct support for closures, but they can emulated using eval mechanisms, in particular, commands uplevel and upvar. For examples, see block-local variables, Custom curry, Serial summing, Deferred evaluation.


First example. The following command accept a block of code and executes it in specified file catalog.

proc temp-chdir {dir block} {
    set cd [pwd]
    cd $dir
    uplevel $block
    cd $cd
}

# sample usage:
temp-chdir / {puts "I'm in [pwd]"}

With upvar and uplevel it is simple to write custom iterators over some structure, like this:

proc foreach-pocket {pw block} {
    upvar 1 $pw pwvar
    foreach p [get-container-pockets] {
        set pwvar $p
        uplevel 1 $block
    }
}

# sample usage
foreach-pocket p {
    puts "Processing $p"
}

mfi


Another example of such style of code can be found on How do I read and write files in Tcl page.


Arjen Markus - Could closures not be simulated very elegantly using the [interp alias] command? As this allows you to associate additional arguments to a command, it seems to me that one can capture local state that way. Just a thought, as I have not worked out the details yet, certainly not in relation to the above examples.

Donal Fellows - Note that the namespace code command creates something very much like closures, the main differences being that the variables and commands in the "closure" are accessible through other means and the "closure" must be explicitly deleted. (Perl also has a way of giving the effect of closures with my variables.)

mfi - See also: Closures. May be merge the pages somehow?

DKF: This sort of thing can be done more safely with the help of try:

proc temp-chdir {dir block} {
    set cd [pwd]
    cd $dir
    try {
        uplevel $block
    } finally {
        catch {cd $cd}
    }
}

This is safer because it tries to undo things even on an error, and doesn't keel over horribly if the original directory is ripped out from under its feet.