Based on your response to my comments to the original question, I recommend you learn to use eval properly instead of trying to create an apply function that works the way you think it should. My reasoning is that if you don't understand eval you don't have enough knowledge to understand how to create and use the apply command.
Believe it or not, your implementation of the apply command is more-or-less correct, but you were using it incorrectly. To describe how and why to use it properly is not worth the trouble when there are other ways to solve the problem.
Your problem boils down to this: you're given a function and N arguments, and you need a way to call that function with exactly N arguments. The proper solution for that is to use eval.
Here's how I would rewrite your log function. I took the liberty of adding code to actually print out the result rather then compute it and return it like your code did. I also added code to print out the error level:
proc log { tag msg args } {
global levels
global LOG_LEVEL
# Filter out any messages below the logging severity threshold.
if { $levels($LOG_LEVEL) <= $levels($tag) } then {
set result [eval format \$msg $args]
puts "$LOG_LEVEL: $result"
}
}
Some important points to understand here. First, the word 'args' is special, and means that all additional arguments are collected into a list. So, whether you call log with zero arguments, one argument, or N arguments, args is a list and will always be a list, even if it's a list of zero or one values.
As you've discovered, the format command (potentially) needs N arguments rather than a list of N arguments. The way around this in Tcl is to use the eval statement. The simplistic explanation is that eval causes a line to be parsed twice.
This is good for $args in that it effectively removes one level of "listness" -- what was a list of N items becomes N distinct items. However, you don't want $msg to be parsed twice because it's not a list of N items. That is why there's a backslash in front of the $ -- it hides the dollar sign from the first pass of the parser. Some people prefer [list $msg], and there are other ways to accomplish the same task.
(note that in this specific case with this specific code, there's no problem in $msg getting parsed twice. It's good practice to always protect things you don't explicitly want expanded when using eval, for reasons not worth getting into here).
Next, we have to turn our attention to the other log functions. They work similarly, and need a similar treatment. These are all essentially pass-through commands, adding one extra argument. Here's how logInfo should look, again using eval:
proc logInfo {msg args} {
eval log INFO \$msg $args
}
Again notice that $msg has a backslash in front of it. This is for the same reason as above -- we want the extra round of parsing for $args but not for $msg.
With those two changes, your code works.
However, there's an arguably better way to implement the logX functions. Since all you're doing is adding an extra argument and then passing everything else as-is to the log function you can take advantage of the interpreter's ability to create aliases. For example:
interp alias {} logTrace {} log TRACE
interp alias {} logDebug {} log DEBUG
interp alias {} logInfo {} log INFO
interp alias {} logWarn {} log WARN
interp alias {} logError {} log ERROR
In the above code, the curly braces simply mean "in the current interpreter". Tcl has the ability to have multiple interpreters running, but that's not important to the matter at hand. When you call logTrace, for example, Tcl will actually call 'log TRACE' and then append any additional arguments on to the end. So, 'logTrace foo bar' becomes 'log TRACE foo bar'.
You are concerned with porting a large body of LISP code to Tcl and want to do as few mental gymnastics as possible, which is understandable. I think it's probably safe to say in your specific case, wherever you see apply in the LISP code you can just replace it with "eval". Then take the extra step of protecting things that don't require any extra parsing.