Как это сделать в Tcl 8.4? - PullRequest
       40

Как это сделать в Tcl 8.4?

1 голос
/ 21 сентября 2009

В Tcl 8.5 я могу сделать что-то вроде этого:

apply llength { 1 2 3 }

Но это не определено в v8.4.

Как бы определить определение применения с использованием Tcl в v8.4?

Мне это нужно, потому что я конвертирую некоторый код LISP в Tcl. В коде lisp есть несколько конструкций, которые я хотел бы перенести так:

array set levels {
  TRACE  0
  DEBUG  1
  INFO   2
  WARN   3
  ERROR  4
}

set LOG_LEVEL INFO
proc setLogLevel { level } {
  global LOG_LEVEL

  set LOG_LEVEL $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 {
    apply format $msg $args
  }
}

proc logTrace { msg args } {
  apply log TRACE $msg $args
}
proc logDebug { msg args } {
  apply log DEBUG $msg $args
}
proc logInfo { msg args } {
  apply log INFO $msg $args
}
proc logWarn { msg args } {
  apply log WARN $msg $args
}
proc logError { msg args } {
  apply log ERROR $msg $args
}

# Close solution (not quite correct)
proc apply {func args} {
  eval [list $func] $args
}

# Example usage:
set instName "myInst"
set viewName "myView"
set cellName "myCell"
logError "Divide by zero."

# Filtered message:
logTrace "Evaluating callbacks for instance %s." $instName
# Enable that same message
setLogLevel TRACE
logTrace "Evaluating callbacks for instance %s." $instName

# This one fails with apply definition given here
logInfo "Opening cellView %s@%s." $viewName $cellName

Спасибо.

-William

Ответы [ 4 ]

5 голосов
/ 22 сентября 2009

Исходя из вашего ответа на мои комментарии к исходному вопросу, я рекомендую вам научиться правильно использовать 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». Затем сделайте дополнительный шаг защиты вещей, которые не требуют дополнительного разбора.

0 голосов
/ 21 сентября 2009

Я почти уверен, что вашему решению придется использовать eval, и, возможно, uplevel, если вы хотите, чтобы оно работало на процессах, использующих uplevel или upvar.

0 голосов
/ 22 сентября 2009

Краткий ответ:

Если у вас есть имя команды в переменной, вы можете запустить ее, поместив переменную в качестве первого слова строки:

set mycommand puts
$mycommand "hello world"

Более длинный ответ:

У вас есть аргументы, которые вы хотите расширить, не нарушая крайние варианты для вашей команды, поэтому вы можете использовать eval для «повторного анализа» строки после ее создания. По сути, вы можете использовать «eval» для раскрытия всех аргументов, а затем использовать «list» для защиты определенных аргументов от раскрытия

% proc {my llength of args} {args} { return [llength $args] }
% set mycommand {my llength of args}
% set args "1 2 3"
% eval $mycommand $args ;# Expands the command, so may blow up
ambiguous command name "my": {my llength} {my llength of args}
% eval [list $mycommand] [list $args] ;# protect the args, so it's not expanded, not what you want
1
% eval [list $mycommand] $args ;# Protect the things you don't want expanded (command named), but allow the args to be expanded to individual arguments
3
0 голосов
/ 21 сентября 2009

Удивительно, но в Tcl он называется apply .

...