如何在Tcl 8.4中进行申请?

发布于 2024-08-04 22:05:58 字数 1435 浏览 10 评论 0原文

在 Tcl 8.5 中我可以做这样的事情:

apply llength { 1 2 3 }

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

在 v8.4 中如何使用 Tcl 定义 apply?

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

谢谢。

——威廉

In Tcl 8.5 I can do something like this:

apply llength { 1 2 3 }

But that apply is not defined in v8.4.

How would I define apply using Tcl in v8.4?

I need this because I am converting some lisp code to Tcl. The lisp code has some constructs that I would like to port like this:

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

Thanks.

-William

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

祁梦 2024-08-11 22:05:58

根据您对我对原始问题的评论的回复,我建议您学习正确使用 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 都是一个列表,并且始终是一个列表,即使它是一个包含零个或一个值的列表。

正如您所发现的,format 命令(可能)需要 N 个参数,而不是 N 个参数的列表。在 Tcl 中解决这个问题的方法是使用 eval 语句。简单的解释是 eval 导致一行被解析两次。

这对 $args 有好处,因为它有效地消除了一级“listness”——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 代码中看到 apply 的任何位置,都可以将其替换为“eval”。然后采取额外的步骤来保护不需要任何额外解析的内容。

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.

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

Surprisingly enough, it's called apply in Tcl, as well.

红尘作伴 2024-08-11 22:05:58

我很确定您的解决方案必须使用 eval,如果您希望它在使用 uplevel 的进程上工作,也许还需要使用 uplevel > 或 upvar

I’m pretty sure your solution is going to have to use eval, and maybe uplevel too if you want it to work on procs that use uplevel or upvar.

独行侠 2024-08-11 22:05:58

简短回答:

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

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

Short answer:

If you have the command name in a variable, you can run it by placing the variable as the first word of the line:

set mycommand puts
$mycommand "hello world"

Longer answer:

You have arguments you want expanded, without breaking the edge cases for your command, so you can use eval to "reparse" the line once you create it. Basically, you can use "eval" to expand all the arguments, then use "list" to protect certain ones from expansion

% 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文