Version 5 of command prefix

Updated 2008-09-18 13:58:06 by mig

What is a command prefix?

A command prefix is a prefix of a command (in the sense of rule 2 of the dodekalogue, i.e., a sequence of words) that is constructed with the expectation that zero or more arguments will be appended to it and that the resulting command will be evaluated. This is very often done repeatedly, with the arguments completing the command being different for each evaluation.

The classical (although not very efficient) example of this is the -command option of lsort, which takes a command prefix as argument. This command prefix is supposed to take two arguments and compare them according to some custom order, returning -1, 0, or 1 according to how the comparison came out. The equally classical example of such a command option is string compare, so one can say

 lsort -command {string compare} {a B c D 0}

which however is just the same as lsort {a B c D 0}. A more interesting choice is package vcompare, which compares version numbers:

 % lsort -command {package vcompare} {2.0.1 3 1.10 1.9 2a0}
 1.9 1.10 2a0 2.0.1 3

The -dictionary option of lsort is rather close, but it wouldn't get the alpha and beta versions right:

 % lsort -dictionary {2.0.1 3 1.10 1.9 2a0}
 1.9 1.10 2.0.1 2a0 3

In order to see (at least some of) how the sorting gets done, we can define a procedure that calls some other command prefix to do the actual sorting, writes the result to stdout, and then returns it:

 proc showsort {prefix a b} {
    set res [{*}$prefix $a $b]
    puts "$a [expr {$res<0 ? "<" : $res>0 ? ">" : "="}] $b"
    return $res
 }

For example,

 % lsort -command {showsort {string compare}} {a B c D 0}
 a > B
 c > D
 B < D
 a > D
 a < c
 B > 0
 0 B D a c
 % lsort -command {showsort {package vcompare}} {2.0.1 3 1.10 1.9 2a0}
 2.0.1 < 3
 1.10 > 1.9
 2.0.1 > 1.9
 2.0.1 > 1.10
 1.9 < 2a0
 1.10 < 2a0
 2.0.1 > 2a0
 1.9 1.10 2a0 2.0.1 3

This particular technique — to let one or more of the words in a command prefix themselves be command prefixes that handle some subtask — can become very powerful if you have several command prefixes that do related things, much for the same reasons as pipelines make the Unix command-line powerful.

Command prefix flavours

Modern command prefixes are typically lists, i.e., they are supposed to be called as

 {*}$prefix $arg1 $arg2 ...

but classical command prefixes has sometimes rather been called effectively as

 eval $prefix [list $arg1 $arg2 ...]

These differ with respect to how they interpret characters that have special meaning in scripts but not in lists — the eval variant treats the prefix more as a "script prefix" than a list prefix. The most common use of this is in an idiom for trace callbacks where one isn't interested in the extra arguments, and ends the prefix with "; #" to end the command and have the arguments that are appended ignored as part of a comment:

  trace add variable localGuardVariable unset "rename someCommand {} ; #"

The more modern way to achieve this effect is however to use apply:

  trace add variable localGuardVariable unset [list ::apply {args {rename someCommand ""} ::}]

NEM Alternatively (and pre-apply) you can use a simple wrapper command that discards any other arguments:

 proc discard {cmd args} { {*}$cmd }
 trace add variable foo unset [list discard {rename someCommand ""}]

[Find and include links to discussion of non-list command prefix flavours — not all of these are the same, either.]

Command prefixes in comparison

[Compare to scripts, commands, and lambdas.]

The main difference between scripts and command prefixes is that the latter typically take input (the arguments appended), whereas scripts do not. Hence if you need to communicate any information in to a script when evaluating it, you need to supply that through the context (e.g. store in variables with predetermined names). Scripts are also unlike the others in that they don't provide any local environment.

In general, one uses list to form a command prefix and {*} to expand it, but beyond that:

  • interp alias can be used to turn a command prefix into a named command.
  • apply can be used to turn a lambda into a command prefix.

NEM Note that a command prefix can be handled much more efficiently than a general script. Under the hood a command prefix can be resolved to the Tcl_Command that implements it and this can then be invoked directly with little or no interpretive overhead. A general script however must be evaluated every time. While the parsing can be cached, there is still some overhead to resolve the command afresh each time. This is why things like lsort -command are slow: while they are only really used with command prefixes they are actually documented as treating the argument as a script, and so need to call eval. Hopefully this can be changed at some point. Any new commands should take command prefixes and invoke them as such (using {*}). Ideally, Tcl needs an invoke command that does the same as uplevel but treats its argument as a command prefix rather than a general script.

MS Please note the special case of scripts which are canonical lists, ie, which was generated using list or any other of the list operations. The core recognizes that these are pre-parsed single commands and optimizes accordingly (if still not optimally) in many cases including eval, uplevel and namespace eval.

It would be possible to do things so that lsort -command and other such cases (notably traces) also recognize this special structure. Actually, it is not a matter of the structure being recognized, we just need not to spoil it when present.