1

在 Tcl 8.5 中,我可以这样做:

apply llength { 1 2 3 }

但是这个 apply 在 v8.4 中没有定义。

我将如何在 v8.4 中使用 Tcl 定义应用?

我需要这个,因为我正在将一些 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

谢谢。

-威廉

4

4 回答 4

5

根据您对我对原始问题的评论的回复,我建议您学习正确使用 eval 而不是尝试创建一个按您认为应该的方式工作的 apply 函数。我的理由是,如果您不了解 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”这个词很特殊,意味着所有额外的参数都被收集到一个列表中。因此,无论您使用零参数、一个参数还是 N 个参数调用 log,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 函数。由于您所做的只是添加一个额外的参数,然后将其他所有内容按原样传递给 log 函数,因此您可以利用解释器创建别名的能力。例如:

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”。然后采取额外的步骤来保护不需要任何额外解析的东西。

于 2009-09-22T18:21:21.500 回答
0

令人惊讶的是,它在 Tcl 中也被称为apply 。

于 2009-09-21T18:07:49.160 回答
0

我很确定您的解决方案将不得不使用,如果您希望它在使用or的 procs 上工作eval,也许也是如此。upleveluplevelupvar

于 2009-09-21T18:22:08.570 回答
0

简短的回答:

如果变量中有命令名称,则可以通过将变量作为行的第一个单词来运行它:

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
于 2009-09-21T20:23:11.613 回答