在Tcl 8.5中,我可以这样做:
apply llength { 1 2 3 }
但是这个适用并没有在v8.4中定义.
如何在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
谢谢.
-威廉
根据您对原始问题的评论的回复,我建议您学会正确使用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"这个词是特殊的,意味着所有其他参数都被收集到一个列表中.因此,无论您使用零参数,一个参数还是N个参数调用log,args都是一个列表,并且始终是一个列表,即使它是一个零或一个值的列表.
正如您所发现的,format命令(可能)需要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"替换它.然后采取额外的步骤来保护不需要任何额外解析的东西.