Исходя из вашего ответа на мои комментарии к исходному вопросу, я рекомендую вам научиться правильно использовать eval вместо того, чтобы пытаться создать функцию применения, которая работает так, как вы думаете. Я рассуждаю так: если вы не понимаете eval, у вас недостаточно знаний, чтобы понять, как создать и использовать команду apply.
Хотите верьте, хотите нет, ваша реализация команды apply более или менее верна, но вы использовали ее неправильно. Описывать, как и зачем правильно его использовать, не стоит беспокоиться, когда есть другие способы решения проблемы.
Ваша проблема сводится к следующему: у вас есть функция и N аргументов, и вам нужен способ вызова этой функции с ровно N аргументами. Правильное решение для этого заключается в использовании eval.
Вот как я бы переписал вашу функцию журнала. Я взял на себя смелость добавить код, чтобы распечатать результат, а не вычислять и возвращать его, как это сделал ваш код. Я также добавил код для вывода уровня ошибки:
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"
}
}
Некоторые важные моменты, которые нужно понять здесь. Во-первых, слово «args» является специальным и означает, что все дополнительные аргументы собраны в список. Таким образом, независимо от того, вызываете ли вы log с нулевым аргументом, одним аргументом или N аргументами, args является списком и всегда будет списком, даже если это список из нуля или одного значения.
Как вы обнаружили, команде форматирования (потенциально) требуется N аргументов, а не список из N аргументов. Способ обойти это в Tcl - использовать оператор eval. Упрощенное объяснение состоит в том, что eval вызывает двойной анализ строки.
Это хорошо для $ args, так как эффективно удаляет один уровень «списанности» - то, что было списком из N элементов, становится N отдельными элементами. Однако вы не хотите, чтобы $ msg анализировался дважды, потому что это не список из N элементов. Вот почему перед $ стоит обратный слеш - он скрывает знак доллара с первого прохода парсера. Некоторые люди предпочитают [list $ msg], и есть другие способы выполнить ту же задачу.
(обратите внимание, что в данном конкретном случае с этим конкретным кодом нет проблем с повторным анализом $ msg. Рекомендуется всегда защищать вещи, которые вы явно не хотите раскрывать, при использовании eval, по причинам, не стоящим здесь ).
Далее, мы должны обратить наше внимание на другие функции журнала. Они работают одинаково и нуждаются в аналогичном лечении. Все это по существу сквозные команды, добавляющие один дополнительный аргумент. Вот как должен выглядеть logInfo, снова используя eval:
proc logInfo {msg args} {
eval log INFO \$msg $args
}
Снова обратите внимание, что перед $ msg стоит обратная косая черта. Это по той же причине, что и выше - мы хотим дополнительный раунд анализа для $ args, но не для $ msg.
С этими двумя изменениями ваш код работает.
Однако есть, возможно, лучший способ реализовать функции logX. Поскольку все, что вы делаете, - это добавление дополнительного аргумента, а затем передача всего остального как есть функции журнала, вы можете воспользоваться способностью интерпретатора создавать псевдонимы. Например:
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
В приведенном выше коде фигурные скобки просто означают «в текущем интерпретаторе». Tcl имеет возможность работать с несколькими переводчиками, но это не важно для рассматриваемого вопроса. Например, когда вы вызываете logTrace, Tcl фактически вызывает 'log TRACE' и затем добавляет любые дополнительные аргументы в конец. Таким образом, 'logTrace foo bar' становится 'log TRACE foo bar'.
Вы обеспокоены переносом большого количества кода LISP в Tcl и хотите сделать как можно меньше умственных упражнений, что понятно. Я думаю, что в вашем конкретном случае можно с уверенностью сказать, что где бы вы ни применяли код LISP, вы можете просто заменить его на «eval». Затем сделайте дополнительный шаг защиты вещей, которые не требуют дополнительного разбора.