What does {*} do in TCL?

Noah picture Noah · Feb 26, 2011 · Viewed 8.4k times · Source

I have used some TCL, but this construction stumps me.

When $res = "Table does not exist", what will the following return?

[list [list {*}$res]]

I know what [list [list $res]] would do, but the extra {*} just baffles me.

Thanks for helping.

Answer

Donal Fellows picture Donal Fellows · Feb 28, 2011

When $res = "Table does not exist", what will the following return?

[list [list {*}$res]]

Well, first know that [list {*}…] is a construct that returns a list of the words in the ellipsis (the contents of the res variable in your case). It happens that in your case, the net effect is nothing as the input string is actually also a well-formed list. That then becomes a single argument to the outer list and so we get a single-element list as result, whose element contains a list of the words Table, does, not and exist in that order, i.e., {Table does not exist}.

Notes on expansion

The list of expanded word form is useful for doing concatenation of lists; the concat command does something similar (but not identical; there are some historical weirdnesses involved in the concat command). Thus, you'd concatenate two lists like this:

set concatenation [list {*}$list1 {*}$list2]

Also note that expansion (introduced in Tcl 8.5) is true syntax, which is a very unusual thing in Tcl. The {*} changes the nature of the following substitution so that it yields multiple words instead of just one. While it is possible to do without it, it's actually remarkably difficult to get right. For example, without it the above would be:

set concatenation [eval [linsert $list1 0 list] [lrange $list2 0 end]]

The introduction of expansion has greatly reduced the number of calls to eval required in most Tcl code (a benefit since it was hard to write properly; a lot of programmers were caught out by the difficulty). This has proved particularly beneficial in practice with the exec command; it makes working with glob and auto_execok much easier:

exec {*}[auto_execok $someCmd] $arg1 {*}[glob *.foo]
# Instead of:
#eval [linsert [auto_execok $someCmd] 0 exec] [linsert [glob *.foo] 0 $arg1]
# Or this _wrong_ version:
#eval exec [auto_execok $someCmd] $arg1 [glob *.foo]

Ugh. That last one was a bit brain-bending to write out in non-expansion form even though I know what I'm doing. (The wrong version is wrong because it falls apart if $arg1 contains Tcl meta-characters…)