Version 8 of invoke

Updated 2007-05-21 16:38:04 by NEM

NEM 9 May 2007: Here is a little proc that I use quite regularly. It is useful for avoiding Quoting Hell in the common case of invoking a callback command with some additional arguments. Typically we expect the callback to consist of one or more words given as a list (e.g. "string match"), which is the usual case for built-in commands (e.g. lsort -command, or after). Usually you'd invoke the command using uplevel, but you have to take care to make sure arguments are quoted correctly while still expanding the command, ending up with some code like:

 uplevel #0 $callback [list $arg1] [list $arg2] ..

This works well, but is a bit hard to read if you are not familiar with the idiom. This is where invoke comes in: it handily packages up this familiar idiom into a single command:

 proc invoke {level cmd args} {
     if {[string is integer -strict $level]} { incr level }
     uplevel $level $cmd $args
 }
 invoke #0 $callback $arg1 $arg2 ...

To be a bit more efficient, we can ensure uplevel is passed a single list, which avoids any possibility of string concatenation (Tcl_UplevelObjCmd does its best to avoid this, but cannot always do so):

 proc invoke {level cmd args} {
     if {[string is integer -strict $level]} { incr level }
     # Note 8.5ism
     uplevel $level [linsert $cmd end {*}$args]
 }

I'm not sure if this really makes a noticeable impact on performance in typical cases, though. (If $cmd doesn't have a string rep, then uplevel does this automatically, from my reading of the source code).

Lars H: As I sort-of remarked in "data is code", a built-in alternative to the #0 case

 uplevel #0 $callback [list $arg1] [list $arg2] ..

is

 namespace inscope :: $callback $arg1 $arg2 ..

This is no good for callbacks that expect to access local variables in their calling context, though; the context is still available (which it wouldn't be with [uplevel #0]), but it's at [uplevel 2], not [uplevel 1] as it would be after a normal call.

Also, in 8.5 the plain case

 invoke 0 $callback $arg1 $arg2 ..

is just

 {*}$callback $arg1 $arg2 ..

NEM: All callbacks I know of in Tcl use the uplevel #0 semantics, so any namespace or callframe context is not available. The callback should take care of restoring any such context by for instance using namespace code or providing any variable values it needs as part of the callback (e.g. [list $callback $var1 $var2...]).


[ Category Example ]