【问题标题】:How to do apply in Tcl 8.4?如何在 Tcl 8.4 中申请?
【发布时间】:2009-09-21 18:01:03
【问题描述】:

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

apply llength { 1 2 3 }

但是 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

谢谢。

-威廉

【问题讨论】:

  • 您实际上不能在 tcl 8.5 中执行“应用 llength {1 2 3}”。 apply 的第一个参数需要是两个或三个元素的列表。您能否举一个真实世界的示例,说明您尝试创建的命令的实际输入和输出是什么?
  • 你想用日志函数做的事情在 Tcl 中是微不足道的,你试图通过编写应用函数来解决它的方式使问题变得困难。您是对使其与 apply 一起工作更感兴趣,还是对更好的方式使您的日志记录工作感到满意?
  • Apply 是首选,因为我有很多 lisp 代码要移植到这里,并且使 Tcl 解决方案尽可能接近 lisp 解决方案可以节省移植时间。不过,我也对您更简单的解决方案感兴趣。

标签: list tcl apply args


【解决方案1】:

根据您对我的 cmets 对原始问题的回答,我建议您学习正确使用 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”。然后采取额外的步骤来保护不需要任何额外解析的东西。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 请记住,[apply] 命令仅在 Tcl 8.5 和更新版本中可用...
    • 这不太行,$args 没有被拆分出来,作为 arg1 传递给应用。
    • 另外我在 8.4,所以这不是一个选项。
    【解决方案3】:

    我很确定您的解决方案将不得不使用 eval,如果您希望它在使用 uplevelupvar 的 proc 上工作,也许还必须使用 uplevel

    【讨论】:

      【解决方案4】:

      简答:

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

      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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-10
        • 2012-03-16
        相关资源
        最近更新 更多